2013년 2월 19일 화요일

[Java] TCP

TCP 프로토콜?
  • TCP는 Socket 과 ServerSocket를 사용하여 데이터를 주고 받습니다.
  • Socket 은 목적지 정보를 전송 수신하는 역할을 합니다.
  • ServerSocket 은 포트 번호와 Client와 연결되는 서버의 최대 연결 수를 지정 할 수 있습니다.
◆ 장점
  • 신뢰성(데이타 손실이 없습니다.)
  • 양뱡향 통신 가능
  • 바이트 스트림 지원
◆ 단점
  • UDP에 비해 다소 느릴 수 있음.
Client 예제
/*  * 클라이언트에서 작업할 내용  
 * 1)소켓 객체 생성(ip, port지정) ==> 해당 ip에 접속하여 정상적인 접속이 완료되면 객체가 생성  
 * 2)I.O 스트림 열기  
 * 3)자료 송/수신  
 * 4)소켓 닫기 
 * */
public class TestClient {
 static Socket s;

 public TestClient() throws IOException {
  s = new Socket("127.0.0.1", 9999); // 서버주소

  BufferedReader reader = new BufferedReader(new InputStreamReader(
    s.getInputStream()));
  PrintWriter writer = new PrintWriter(s.getOutputStream());
  String msg = "Strat!!";
  String rMsg = null;
  BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
  while (!msg.equals("bye")) {
   System.out.print("전송입력 : ");
   msg = in.readLine();
   writer.print(msg);
   writer.flush();
   System.out.println("가는 중...");
   rMsg = reader.readLine();
   System.out.print("받은 메세지:" + rMsg);
  }
 }

 public static void main(String[] args) throws IOException {
  try {
   new TestClient();
  } catch (IOException e) {
   e.printStackTrace();
  } finally {
   try {
    s.close();
   } catch (IOException e) {
    e.printStackTrace();
   }
  }
 }
}

  • 주소와 포트번호와 함께 Socket을 생성 합니다.
  • 바이트 스트림을 지원 하므로 바이트 형태로 데이타를 주고 받습니다.
  • in.readLine()으로 입력 받은 문자를 writer.print(msg)을 사용하여 Server에 보내고 writer.flush() 시켜줍니다.
  • rMsg = reader.readLine()은 Server에서 받은 메세지를 한줄씩 읽습니다.
  • BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream())) 와 PrintWriter writer = new PrintWriter(s.getOutputStream())에서 전송한 데이타와 수신한 데이터를 알 수 있습니다.

Server 예제
/*  * 서버에서 수행하는 일  
 *   1)서버소켓 객체 생성  
 *   2)클라이언트 접속 대기  
 *   3)자료 송/수신  
 *   4)소켓 닫기 */
public class TestServer {
 static ServerSocket ss = null;
 static Socket s = null;

 public TestServer() throws IOException {
  ss = new ServerSocket();
  s = new Socket();
  ss = new ServerSocket(9999);
  System.out.println("클라이언트 접속 대기 중...");
  s = ss.accept();
  System.out.println("클라이언트 접속!!");
  BufferedReader reader = new BufferedReader(new InputStreamReader(
    s.getInputStream()));
  PrintWriter write = new PrintWriter(s.getOutputStream());
  String msg = "Start!";
  while (!msg.equals("bye")) {
   System.out.println("받는 중...");
   msg = reader.readLine();
   System.out.println("수신데이타 : " + msg);
   write.println(msg);
   write.flush();
  }
 }

 public static void main(String[] args) {
  try {
   new TestServer();
  } catch (IOException e) {
   e.printStackTrace();
  } finally {
   try {
    ss.close();
    s.close();
   } catch (IOException e) {
    e.printStackTrace();
   }
  }
 }
}

  • ServerSocket으로 포트 번호를 맞춰 줍니다.
  • s = ss.accept() 부분이 Client가 접속하기를 기다리는 부분입니다. 서버는 클라이언트가 접속하기 전까지 대기하고있는 상태입니다.
  • msg = reader.readLine() 위에 스트림으로 받은 데이터를 다시 Client로 전송하는 부분입니다.

TCP를 설명하기 위해 간단히 작성된 소스입니다. 요약하자면 Client에서 Socket 를 사용하여 목적지(서버)의 주소와 포트번호를 설정하여 보내면 서버에서는 ServerSocket 에서 포트를 맞추고 accept()에서 접속하기를 기달리고 있다가 스트림을 이용하여 다시 전송하는 코드입니다.

관련글
  1. [Java] UDP
  2. [Java] 객체 저장(OutputStream, inputStream, BufferStream)

[Java] UDP

UDP 프로토콜?

  • UDP는 DatagramSocket 와 DatagramPacket를 사용하여 데이터를 전송한다는 것만 알면 됩니다.
  • DatagramSocket은 DatagramPacket를 전송하거나 수신 받기 위해 사용 합니다.
  • DatagramPacket은 UDP 통신에서 데이터를 담아서 전송하거나 받기 위해 사용 합니다.

◆ 장점
  • 복잡하지 않음
  • 시스템 로드를 줄일 수 있음
  • 빠름

◆ 단점
  • 데이타 손실이 있을 수 있음
Client 예제
public class BroadClient {
 public BroadClient() {
  new Thread(new ReadThread()).start();
 }

 class ReadThread implements Runnable {
  DatagramSocket ds = null;
  DatagramPacket packet = null;
  byte[] brr = new byte[200];

  public void run() {
   try {
    ds = new DatagramSocket(12345); // 서버 port를 맞춘다.
    packet = new DatagramPacket(brr, brr.length);// brr : 데이터그램 패킷을 수신할 바이트 배열, brr.length:데이터그램 수신 크기.
    while (true) {
     ds.receive(packet); // 블럭 되어 있는 부분.
     System.out.println("받은 문자열 : "+ new String(packet.getData())); // 바이트로 받은 내용을 문자로바꾼다.
     System.out.println(packet.getAddress().getHostAddress());// 보낸사람 address를 알 수 있다.
     Thread.yield();
    }
   } catch (Exception e) {
    System.out.println("받기 오류 :" + e);
    e.printStackTrace();
   }
  }
 }

 public static void main(String[] args) {
  new BroadClient();
 }
}

  • 수신받는 부분의 DatagramPacket 부분입니다. packet = new DatagramPacket(brr, brr.length)
  • ds.receive(packet) 부분이 Server에서 데이타를 받는 부분입니다. 데이터를 받을때까지 블럭 되어있습니다.

Server 예제
public class BroadServer {
 boolean onAir = false;
 DatagramSocket ds;
 DatagramPacket packet;
 int port = 12345;// 받을 사람 port // 상용화 포트(1024 ~ 10000)

 InetAddress address;

 public BroadServer() {
  HowServer server = new HowServer();
  Thread trd = new Thread(server);
  onAir = true;
  trd.start();
 }

 static class Unse {
  static Random ran = new Random();
  static final String[] arr = { "오늘은 모든 일이 잘 풀릴것 같음.",
    "비가 올 것 같이 기분이 좋지 않음.", "안드로이드 공부 중.", "xml 중요.",
    "프로젝트 준비 철저.", "마무리를 잘하자!!" };

  static String getUnse() {
   return arr[ran.nextInt(arr.length)];
  }
 }

 class HowServer implements Runnable {
  String data;
  byte[] brr;

  public void run() {
   try {
    ds = new DatagramSocket();
    address = InetAddress.getByName("127.0.0.1");// 서버 주소
   } catch (Exception e) {
    e.printStackTrace();
   }
   try {
    while (onAir) {
     data = Unse.getUnse(); // 운세의 내용을 가져온다.
     brr = data.getBytes(); // String을 byte 배열로 만들기.
     packet = new DatagramPacket(brr, brr.length, address, port); // 배열, 배열길이, 주소, 포트번호.
     ds.send(packet);
     System.out.println("서버전송 문자열 :" + data);
     Thread.sleep(1000);
    }
   } catch (Exception e) {
    System.out.println("서버생성 오류 :" + e);
    e.printStackTrace();
   }
  }
 }

 public static void main(String[] args) {
  new BroadServer();
 }
}
  • 전송하는 부분의 DatagramPacket 부부입니다.packet = new DatagramPacket(brr, brr.length, address, port)
  • ds.send(packet) 부분이 Client로 데이타를 보내는 부분입니다.
UDP는 특별한 내용이 없습니다. DatagramSocket 와 DatagramPacket를 사용하며 send()로 보내며 receive()로 받는 다는 것만 기억하시면 됩니다.

관련글
  1. [Java] TCP
  2. [Java] 객체 저장(OutputStream, inputStream, BufferStream)

2013년 2월 17일 일요일

[Java] 객체 저장(OutputStream, InputStream, BufferStream)

객체를 저장하는 방법에는 2가지가 있습니다.
  1. 직렬화를 사용하여 저장
    • 자바 프로그램에서만 사용한다는 조건에서 사용합니다.
    • 클래스를 직렬화 할려면 implements Serializable를 사용해야 됩니다.
    • static 변수는 직렬화 하지 못합니다. 
    • 클래스 중에 직렬화를 하고 싶지 않은 변수에는 transient를 사용합니다.
  2. 문자로 저장
    • 타 프로그램에서도 저장된 객체를 사용 할 수도 있을때 사용합니다.
◆ 직렬화를 사용할 경우
 try {
 //  "dg.ser"로 저장합니다.
  ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("dg.ser"));
  oos.writeObject(d);
  oos.close();
  
 //  파일이름이 "dg.ser"인 파일을 가져옵니다.
  ObjectInputStream ois = new ObjectInputStream(new FileInputStream("dg.ser"));
  d = (DungeonGame)ois.readObject();
  ois.close();
  
 } catch (Exception e) {
  // TODO: handle exception
  e.printStackTrace();
 }

《저장》
  • FileOutputStream를 사용하여 바이트 형태로 저장합니다.
  • ObjectOutputStream를 사용하여 직렬화 하여 저장합니다.
  • ObjectInputStream를 닫아 줍니다. ObjectInputStream를 닫으므로서 밑에 있는 FileOutputStream는 자동으로 닫힙니다.
《가져오기》
  • FileOutputStream를 사용하여 바이트 형태로 읽습니다.
  • ObjectOutputStream를 사용하여 클래스를 찾아서 불러온 다음 저장되었던 인스턴스 변수값을 다시 대입합니다. 직렬화된 객체의 생성자는 실행되지 않습니다.
  • ObjectInputStream를 닫아 줍니다. ObjectInputStream를 닫으므로서 밑에 있는 FileOutputStream는 자동으로 닫힙니다.
◆ 문자로 저장
    try {
     //  "wawoo.txt"로 저장합니다.
      BufferedWriter writer = new BufferedWriter(new FileWriter("wawoo.txt"));
      writer.write("wawoo oops!");
      writer.close();
      
     //  파일 이름이 "wawoo.txt"인 것을 불러옵니다.
      BufferedReader reader = new BufferedReader(new FileReader(new File("wawoo.txt")));
      String line = null;
      while ((line = reader.readLine()) != null) {
       System.out.println(line);
      }
      reader.close();
      
     } catch (Exception e) {
      // TODO: handle exception
      e.printStackTrace();
     }

    《저장》
    • FileWriter를 사용하여 문자 형태로 저장합니다.
    • BufferedWriter를 사용하여 효율을 높입니다. 버퍼를 사용하는 목적중에 하나는 시스템 효율입니다. 버퍼는 box라고 생각하고 박스가 차면 한번에 데이터를 처리한다고 생각하면 됩니다.
    • BufferedWriter를 닫아줍니다.여기서도 역시 BufferedReader만 관리하면 하위단에 있는 FileReader는 자동으로 닫힙니다.
    《가져오기》
    • FileReader를 사용하여 저장된 파일을 연결합니다.
    • BufferedReader를 사용하여 효율을 높입니다.
    • BufferedReader를 닫아줍니다. 

    2013년 2월 6일 수요일

    [Android] Fragment example(프레그먼트 예제)

    Fragment 예제가 제일 잘 나와 있는 곳은 Android API 소스입니다.

    • App → Fragment 에 들어가 보면 많이 나와있습니다.


    사실 Fragment가 만들어지게 된 큰 이유중에 하나가 타블렛에서의 화면 비효율성 때문입니다. 이제는 스마트폰의 크기도 커지면서 LandScape일때와 Portrait일때의 화면 구성도 달라져야 합니다. 예제 중 Fragment의 활용을 가장 잘 보여준다고 생각하는 FragmentLayout 예제를 가지고 Fragment를 설명 하겠습니다.

    Portrait XML(res - layout)
    <?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" >
    
        <fragment
            android:id="@+id/fragment1"
            android:name="com.example.wawoops_fragment_test.FragmentLayoutActivity$TitlesFragment"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    
    </LinearLayout>


    LandScape XML(res - layout-land)
    <?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="horizontal" >
    
        <fragment
            android:id="@+id/fragment1"
            android:name="com.example.wawoops_fragment_test.FragmentLayoutActivity$TitlesFragment"
            android:layout_width="0dip"
            android:layout_height="match_parent" 
            android:layout_weight="1"/>
    
        <FrameLayout
            android:id="@+id/flLandFragmentLayout"
            android:layout_width="0dip"
            android:layout_height="match_parent" 
            android:layout_weight="1">
        </FrameLayout>
    
    </LinearLayout/>
    


    일단 위와 같이 portrait 화면 일 때와 LandScape 화면일때를 구성해 줍니다. 두 파일명은 같아야 합니다. 저는 activity_fragment_layout.xml로 만들어 줬습니다.

    android:name="com.example.wawoops_fragment_test.FragmentLayoutActivity$TitlesFragment" 의 의미는 com.example.wawoops_fragment_test 패키지 안에 FragmentLayoutActivity 클래스 안에 TitlesFragment 라는 Fragment를 상속받은 클래스가 있다는것을 의미 합니다. 이처럼 Fragment Component는 Fragment를 extends 하는 클래스를 만들고 그 경로를 써줘야 됨을 알 수 있습니다.

    두 화면의 차이점은 LandScape 화면일때 옆에 화면을 하나 더 구성하기 위해 FrameLayout을 추가해 주었습니다.

    간단하게 프로세스를 설명 드리자면 Activity 클래스에서 setcontentView(activity_fragment_layout); 와 같이 layout을 호출하면 layout안에 fragment 태그안에서 java 파일의 TitlesFragment 를 호출 하게 됩니다.

    FragmentLayoutActivity$TitlesFragment
    public static class TitlesFragment extends ListFragment{
      boolean mLandPane;
      int mCurCheckPosition = 0;
      @Override
      public void onActivityCreated(Bundle savedInstanceState) {
       // TODO Auto-generated method stub
       super.onActivityCreated(savedInstanceState);
       Log.v(TAG, "onActivityCreated");
       
       setListAdapter(new ArrayAdapter(getActivity(), android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));
       
       View detailsFrame = getActivity().findViewById(R.id.flLandFragmentLayout);
       mLandPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;
       
       if (savedInstanceState != null) {
        mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
       }
       
       if (mLandPane) {
        showDetails(mCurCheckPosition);
       }
      }
      
      @Override
      public void onSaveInstanceState(Bundle outState) {
       // TODO Auto-generated method stub
       super.onSaveInstanceState(outState);
       Log.e(TAG, "onSaveInstanceState");
       outState.putInt("curChoice", mCurCheckPosition);
      }
    
      @Override
      public void onListItemClick(ListView l, View v, int position, long id) {
       // TODO Auto-generated method stub
       showDetails(position);
      }
    
      void showDetails(int index){
       Log.v(TAG, "index : " + index);
       mCurCheckPosition = index;
       getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
       getListView().setItemChecked(index, true);
       
       if (mLandPane) {
        Log.v(TAG, "mLandPane index : " + index);
        DetailsFragment details = (DetailsFragment) getFragmentManager().findFragmentById(R.id.flLandFragmentLayout);
        if (details == null || details.getShowIndex() != index) {
         details = DetailsFragment.newInstance(index);
         
         FragmentTransaction ft = getFragmentManager().beginTransaction();
         ft.replace(R.id.flLandFragmentLayout, details);
         ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
         ft.commit();
        }
       }else {
        Intent intent = new Intent();
        intent.setClass(getActivity(), DetailsActivity.class);
        intent.putExtra("index", index);
        startActivity(intent);
       }
    
      }
     }
    
    • TitlesFragment는 ListFragment를 상속 받았습니다.
    • public void onActivityCreated(Bundle savedInstanceState) 는 Fragment의 생명주기중 화면에 보여지기 바로 직전의 method 입니다.
    • View detailsFrame 에 LandScape 화면일때 상세화면을 보여줄 FrameLayout를 findViewById를 해줍니다. 여기서 getActivity()는 TitlesFragment를 띄우고 있는 Activity를 말합니다. Fragment를 사용하다보면 많이 사용하게 됩니다.
    • mLandPane는 화면이 LandScape일때를 구분해 주는 flag 입니다.
    • public void onListItemClick(ListView l, View v, int position, long id) 는 ListView를 클릭 했을때의 CallBack Method  입니다.
    • void showDetails(int index) 부분이 FragmentLayout 프로젝트의 핵심 부분으로 LandScape 화면일때는 FrameLayout에 DetailsFragment를 매칭시키며 Portrait 화면일 경우에는 DetailsActivity를 호출하는 내용입니다.
      • (DetailsFragment)getFragmentManager().findFragmentById(R.id.flLandFragmentLayout) :  Fragment 안에서 Activity의 findViewById와 마찬가지로 findFragmentById를 사용하여 fragment로 선언해 줄수 있습니다.
      • details = DetailsFragment.newInstance(index) : DetailsFragment클래스안에 static newInstance  method를 만들어 놨습니다.
      • FragmentTransaction ft = getFragmentManager().beginTransaction() : FragmentManager().beginTransaction()를 통해 FragmentTransaction를 가져옵니다.
      • ft.replace(R.id.flLandFragmentLayout, details) : FragmentTransaction를 이용하여 fragment를 Activity에 추가하였다가 삭제할 수 있습니다. 현재는 R.id.flLandFragmentLayout에 details를 replace 하는 Method 입니다.
      • ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) : setTransition는 Fragment 에서 애니메이션 효과를 주는 기능입니다.
      • ft.commit() : 마지막에 꼭 commit 해줘야 화면에서 보입니다.
    참고)
    ◆ setTranstion (int transit) : 애니메이션 효과를 준다.
    • TRANSIT_NONE : 애니메이션 효과를 제공하지 않는다.
    • TRANSIT_FRAGMENT_OPEN : 스택에서 꺼내는 효과를 창출한다.
    • TRANSIT_FRAGMENT_CLOSE : 스택에 넣는 효과를 창출한다.
    ◆ setCustomAnimation (int enter, int exit) : 사용자가 제작한 애니메이션을 사용하여 Fragment를 출력한다.
    • enter : 시작시점 효과
    • exit : 끝나는 시점의 효과
    FragmentLayoutActivity$DetailsFragment
    public static class DetailsFragment extends Fragment {
      
      public static DetailsFragment newInstance(int index){
       
       DetailsFragment f = new DetailsFragment();
       Bundle args = new Bundle();
       args.putInt("index", index);
       f.setArguments(args);
       return f;
      }
      
      @Override
      public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
       // TODO Auto-generated method stub
       if (container == null) {
        return null;
       }
       ScrollView scroller = new ScrollView(getActivity());
       TextView text = new TextView(getActivity());
       int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getActivity().getResources().getDisplayMetrics());
       text.setPadding(padding, padding, padding, padding);
       scroller.addView(text);
       text.setText(Shakespeare.DIALOGUE[getShowIndex()]);
       return scroller;
      }
      
      public int getShowIndex(){
       return getArguments().getInt("index", 0);
      }
     }
    

    DetailsFragment에서 유심히 봐야할 메소드는 setArguments입니다. Activity에서는 Intent.putExtra()와 Intent.getExtra()와 같은 method를 이용하여 데이터를 주고 받았지만 Fragment에서는 setArguments()와 getArguments()메소드를 이용하여 데이터를 주고 받을 수 있습니다.
    • onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) 는 Fragment의 생명주기중 View를 inflater할때 사용할수 있는 곳입니다. 매개변수를 보면 여기에는 화면을 붙일 수 있는 inflater와 부모View를 가지고 올 수 있는 container와 저장된 정보를 사용 할 수 있는 savedInstanceState로 구성되어 있습니다. 하단 코딩은 UI를 자바 코드로 표현한 것입니다.
    • getShowIndex()에서 getArguments()를 사용하여setArguments()한 데이타를 가지고 오도록 했습니다.

    FragmentLayout.java 소스 전문
    package com.example.wawoops_fragment_test;
    
    import android.app.Activity;
    import android.app.Fragment;
    import android.app.FragmentTransaction;
    import android.app.ListFragment;
    import android.content.Intent;
    import android.content.res.Configuration;
    import android.os.Bundle;
    import android.util.Log;
    import android.util.TypedValue;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ArrayAdapter;
    import android.widget.ListView;
    import android.widget.ScrollView;
    import android.widget.TextView;
    
    public class FragmentLayoutActivity extends Activity {
     private static final String TAG = "FragmentLayoutActivity";
    
     @Override
     protected void onCreate(Bundle savedInstanceState) {
      // TODO Auto-generated method stub
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_fragment_layout);
     }
     
     public static class DetailsActivity extends Activity{
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
       // TODO Auto-generated method stub
       super.onCreate(savedInstanceState);
       
       if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
        finish();
        return;
       }
       
       if (savedInstanceState == null) {
    //    DetailsFragment details = new DetailsFragment();
    //    details.setArguments(getIntent().getExtras());
    //    Log.e(TAG, "getIntExtra : " + getIntent().getIntExtra("index", 0));
        DetailsFragment details = DetailsFragment.newInstance(getIntent().getIntExtra("index", 0));
        getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
       }
      }
     }
     
     public static class TitlesFragment extends ListFragment{
      boolean mLandPane;
      int mCurCheckPosition = 0;
      @Override
      public void onActivityCreated(Bundle savedInstanceState) {
       // TODO Auto-generated method stub
       super.onActivityCreated(savedInstanceState);
       Log.v(TAG, "onActivityCreated");
       
       setListAdapter(new ArrayAdapter(getActivity(), android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));
       
       View detailsFrame = getActivity().findViewById(R.id.flLandFragmentLayout);
       mLandPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;
       
       if (savedInstanceState != null) {
        mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
       }
       
       if (mLandPane) {
        showDetails(mCurCheckPosition);
       }
      }
      
      @Override
      public void onSaveInstanceState(Bundle outState) {
       // TODO Auto-generated method stub
       super.onSaveInstanceState(outState);
       Log.e(TAG, "onSaveInstanceState");
       outState.putInt("curChoice", mCurCheckPosition);
      }
    
      @Override
      public void onListItemClick(ListView l, View v, int position, long id) {
       // TODO Auto-generated method stub
       showDetails(position);
      }
    
      void showDetails(int index){
       Log.v(TAG, "index : " + index);
       mCurCheckPosition = index;
       getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
       getListView().setItemChecked(index, true);
       
       if (mLandPane) {
        Log.v(TAG, "mLandPane index : " + index);
        DetailsFragment details = (DetailsFragment) getFragmentManager().findFragmentById(R.id.flLandFragmentLayout);
        if (details == null || details.getShowIndex() != index) {
         details = DetailsFragment.newInstance(index);
         
         FragmentTransaction ft = getFragmentManager().beginTransaction();
         ft.replace(R.id.flLandFragmentLayout, details);
         ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
         ft.commit();
        }
       }else {
        Intent intent = new Intent();
        intent.setClass(getActivity(), DetailsActivity.class);
        intent.putExtra("index", index);
        startActivity(intent);
       }
    
      }
     }
     
     public static class DetailsFragment extends Fragment {
      
      public static DetailsFragment newInstance(int index){
       
       DetailsFragment f = new DetailsFragment();
       Bundle args = new Bundle();
       args.putInt("index", index);
       f.setArguments(args);
       return f;
      }
      
      @Override
      public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
       // TODO Auto-generated method stub
       if (container == null) {
        return null;
       }
       ScrollView scroller = new ScrollView(getActivity());
       TextView text = new TextView(getActivity());
       int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getActivity().getResources().getDisplayMetrics());
       text.setPadding(padding, padding, padding, padding);
       scroller.addView(text);
       text.setText(Shakespeare.DIALOGUE[getShowIndex()]);
       return scroller;
      }
      
      public int getShowIndex(){
       return getArguments().getInt("index", 0);
      }
     }
    }
    }
    

    API 소스중 App → Fragment → FragmentLayout 의 소스이며 약간 수정한 부분이 있습니다.
    수정한 부분은 API에서는 DetailsActivity에서 DetailsFragment를 새로 만들어서 사용하던것을 newInstance를 활용하는 방법을 사용 하였으며, 자동화면 전환 시 마지막 클릭한 정보를 Portrait 와 LandScape와 상관없이 알고 있다가  LandScape 화면 전화시 보여주게 하기 위하여 getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE), getListView().setItemChecked(index, true)의 위치를 수정 하였습니다.

    관련글

    2013년 2월 5일 화요일

    [Android] Fragment 화면연결

    Fragment를 Activity와 연결하기 위해서는 FragmentManager 객체와 FragmentTransaction 객체 가 필요하다.
    화면구성순서는 아래와 같다.

    1. Fragment Class를 생성해 준다.
    2. getFragmentManager() Method를 통해 FragmentManager객체를 생성한다.
    3. FragmentManager 객체의 beginTransaction() 메소드를 호출하여 FragmentTransaction을 생성해준다.
    4. FragmentTransaction 객체의 add() 메소드를 이용하여 Fragment를 추가해준다.
    5. commit()를 호출해 주면 연결이 된다. 
    • commit()이 호출된 이후 바로 Fragment를 호출해 주는 것이 아니므로 사양이 낮은 Tablet은 시간차가 발생할 수 있다. 즉시 화면이 출력하고자 한다면 FragmentManager.executePendingTransactions() method를 호출하면 된다.
    ◆ 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”>
    
        <FrameLayout
            android:id=”@+id/fragmentLayout”
            android:layout_width=”match_parent”
            android:layout_height=”match_parent”>
        </FrameLayout>
    
    </LinearLayout>
    


    ◆ Activity 화면
    public class LifeCycleOfFragmentActivity extends Activity {
     private static final String TAG = "LifeCycleOfFragment";
    
     @Override
     protected void onCreate(Bundle savedInstanceState) {
      // TODO Auto-generated method stub
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_lifecycle_fragment);
      Log.e(TAG, "Activity >> onCreate");
    
      WawooPsFragment wf = new WawooPsFragment();
      FragmentTransaction ft = getFragmentManager().beginTransaction();
      ft.add(R.id.fragmentLayout, wf).commit();
    
     }
    
     @Override
     protected void onStart() {
      // TODO Auto-generated method stub
      super.onStart();
      Log.e(TAG, "Activity >> onStart");
     }
    
     @Override
     protected void onResume() {
      // TODO Auto-generated method stub
      super.onResume();
      Log.e(TAG, "Activity >> onResume");
     }
    
     @Override
     protected void onPause() {
      // TODO Auto-generated method stub
      super.onPause();
      Log.e(TAG, "Activity >> onPause");
     }
    
     @Override
     protected void onStop() {
      // TODO Auto-generated method stub
      super.onStop();
      Log.e(TAG, "Activity >> onStop");
     }
    
     @Override
     protected void onDestroy() {
      // TODO Auto-generated method stub
      super.onDestroy();
      Log.e(TAG, "Activity >> onDestroy");
     }
    }
    

    FragmentTransaction.add() method의 매개변수이다.

    public abstract FragmentTransaction add (int containerViewId, Fragment fragment)
    public abstract FragmentTransaction add (int containerViewId, Fragment fragment, String tag)

    • containerViewId : Fragment를 식별하는 사용하는 Unique한 ID가 된다. 만약  Id를 0으로 설정하며 되면 Activity에 Fragment가 등록되지 않는다.
    • fragment : Activity와 연결 시킬 fragment이다.
    • tag : Unique한 ID 를 대신하여 Fragment를 찾는데 사용하는 옵션이다. 


    ◆ Fragment 화면
    public class WawooPsFragment extends Fragment {
     private static final String TAG = "WaooPsFragment";
    
     @Override
     public void onAttach(Activity activity) {
      // TODO Auto-generated method stub
      super.onAttach(activity);
      Log.d(TAG, "Fragment >> onAttach");
     }
    
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
      // TODO Auto-generated method stub
      super.onActivityCreated(savedInstanceState);
      Log.d(TAG, "Fragment >> onActivityCreated");
     }
    
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
       Bundle savedInstanceState) {
      // TODO Auto-generated method stub
      Log.d(TAG, "Fragment >> onCreateView");
      return super.onCreateView(inflater, container, savedInstanceState);
     }
    
     @Override
     public void onCreate(Bundle savedInstanceState) {
      // TODO Auto-generated method stub
      super.onCreate(savedInstanceState);
      Log.d(TAG, "Fragment >> onCreate");
     }
    
     @Override
     public void onStart() {
      // TODO Auto-generated method stub
      super.onStart();
      Log.d(TAG, "Fragment >> onStart");
     }
    
     @Override
     public void onResume() {
      // TODO Auto-generated method stub
      super.onResume();
      Log.d(TAG, "Fragment >> onResume");
     }
    
     @Override
     public void onPause() {
      // TODO Auto-generated method stub
      super.onPause();
      Log.d(TAG, "Fragment >> onPause");
     }
    
     @Override
     public void onStop() {
      // TODO Auto-generated method stub
      super.onStop();
      Log.d(TAG, "Fragment >> onStop");
     }
    
     @Override
     public void onDestroyView() {
      // TODO Auto-generated method stub
      super.onDestroyView();
      Log.d(TAG, "Fragment >> onDestroyView");
     }
    
     @Override
     public void onDestroy() {
      // TODO Auto-generated method stub
      super.onDestroy();
      Log.d(TAG, "Fragment >> onDestroy");
     }
    
     @Override
     public void onDetach() {
      // TODO Auto-generated method stub
      super.onDetach();
      Log.d(TAG, "Fragment >> onDetach");
     }
    }
    


    Fragment LifeCycle은 이전에 포스팅 했지만 코드 작성을 한김에 한번 확인 보면 아래 와 같이 나타 난다.

    Activity_onCreate() Fragment_onAttach() ▶ Fragment_onCreate() ▶ Fragment_onCreateView ▶ Fragment_onActivityCreated() ▶ Activity_onStart() ▶ Fragment_onStart() ▶ Activity_onResume() ▶ Fragment_onResume() ▶ Fragment_onPause() ▶ Activity_onPause() ▶ Fragment_onStop() ▶ Activity_onStop() ▶ Fragment_onDestoryView() ▶ Fragment_onDestory() ▶ Fragment_Detach() ▶ Activity_onDestory() 

    관련글 
    1. 프레그먼트 생명주기(Fragment LifeCycle)
    2. [Android]Fragment example(프레그먼트 예제)

    2013년 2월 4일 월요일

    [Android] Fragment LifeCycle (프레그먼트 생명주기)

    ◆ Fragment 란?
    Tablet이 나오기 전에는 화면의 크기가 그리 크지 않았기 때문에 Activity에서 하나의 화면만 보여주면 됐습니다. 하지만 Tablet이 나오면서 부터 화면을 분할해야 할 필요성이 생겼습니다. 같은 디자인을 Tablet에서 보면 공간 효율이 너무 좋지 않았습니다. 여백에 미도 좋긴 하지만 공간을 디자인 하는 입장에서 보면 화면 구성이 너무 비효율적이였습니다. 그래서 생긴 개념이 Fragment 입니다.

    사실 허니콤이 나왔을때 까지만 해도 타블렛 앱을 개발할 일이 없어서 Fragment를 눈여겨 보지는 않았지만 아이스크림 샌드위치 이후 타블렛과 스마트폰의 운영체계가 합쳐지면서 Fragment를 사용 안할 수 없게 되었습니다. 물론 업데이트 될때마다 기존에 있든 API들이 하나씩 Deprecated 되면서 Fragment 사용을 유도하고 있는 것 또한 이제는 Fragment를 사용하지 않으면 안되지 않을까 생각합니다. 




    • Fragment는 Activity와 1:1 개념이 아닌 1:N 의 개념입니다.
    • Fragment는 Activity와 비슷한 개념이 아닌 Activity에 안에 항상 포함되어 있는 개념입니다.
    ◆ Fragment LifeCycle
    Activity에도 생명주기가 있듯이 Fragment에도 생명주기가 있습니다. Activity보다 다서 많은 것 같지만 크게 보면 똑같습니다. 다만 세부적으로 들어가면 몇가지 추가 되기는 했지만 생각해 보면 더 편리해 진거 같습니다.
    아래는 Activity 생명주기와 Fragment의 생명주기를 비교해 놓은 그림입니다.


    그림을 보시면 아시겠지만 다른 생명주기는 똑같습니다. 다만 Activity의 onCreate() 와 onDestory() method가 좀더 세분화 되었습니다. 아래 그림은 Fragment의 생명주기 입니다.



    • onAttach() : Fragment가 Activity에 연결되기 전에 호출된다. Fragment는 아직 생성되지 않았다.
    • onCreate() : Fragment가 생성되는 시점에 호출된다. 하지만 아직 연결되어 있지는 않는다.
    • onCreateView() : 화면에 출력할 레이아웃을 Inflate 할 수 있도록 제공되는 옵션적인 성격의 CallBack Method이다.
    • onActivityCreated() : Fragment와 Activity가 연결되는 시점이다.
    • onStart() ~ onStop() : Activity 생명주기와 똑같다.
    • onDestroyView() : Fragment View를 제거하는 곳이다.
    • onDestory() : Fragment 객체를 제거하기 전이다.
    • onDetach() : Fragment 객체가 제거 된 후 Activity와 연결관계를 삭제할 때 호출된다.