2013. 12. 2. 13:54

세션의 유효시간은 디폴트로 30분으로 설정 되어있다.

C:\Tomcat\conf\web.xm   파일을 열어서 수정이 가능 하다

<session-config>

     <session-timeout>30</session-timeout>

 </session-config>


코드에서 설정 하는 법 

session.setMaxInactiveInterval(30) : 30초후 세션 삭제.(30초 후, refresh하면 ID 변경)

session.invalidate() : 세션 바로 삭제.

Posted by hoonihoon
2013. 12. 2. 13:41

HTTP 개념

1. HTTP 는 비연결 구조로 사용자의 연결을 계속 유지 하지 않는 방식

2. 사용자의 요청에 대한 응답을 한 후 연결을 해제


아래글은 쿠키와 세션을 이해하기 쉽게 그림으로 설명 해 놓은 글입니다. 


로그인 정보나 장바구니 정보 같은 클라이언트 기반 정보들은 어떻게 저장할까?

다음 그림을 보자.

HTTP는 연결 보장을 하지 않으므로 또 다시 요청이 들어 온다면 서버는 해당 브라우저의 세션정보를 찾아줘야 할 것이다.

그런데 서버입장에서 생각해보면 서버에 요청해놓은 브라우저들이 엄청 많기에 세션정보를 어떻게 줄까?


그래서 자신의 세션 정보를 찾기 위해 자신의 세션을 증명하는 ID 가 필요하다.

보통의 사이트에서는 ID를 쿠키로 가지고 있게 된다. 

쿠키란 브라우저 상에서 저장되는 객체이다.

더쉽게 말하면 쿠키는 사용자 컴퓨터의 하드디스크에 저장되는 작은 텍스트 파일이며 세션은 서버의 메모리상에 존재하는 객체이다.

사용자 컴퓨터에서 세션정보처럼 많은 정보를 저장하기 힘든 관계로 많은 정보는 세션에 저장하고 그 세션 아이디만 web browser(사용자pc) 에 쿠키 형태로 저장을 해두는 것이다.


이런식으로 하면 세션아이디로 로그인 정보를 꺼내 줄 수 있다.

그럼 한번 이 세션아이디로 정말 로그인이 가능한지 테스트 해볼까?



일단 쿠키를 수정하기 위해 크롬 ad-on "Edit This Cookie" 를 설치한다.



원하는 사이트에 로그인 한 후 Edit This Cookie 를 실행해 보면



앵간하면 SESSION ID 라고 써있는 쿠키를 발견할 수 있을 것이다.


SESSION ID 라고 써있는 쿠키를 발견했다면 그 값을 잠시 메모장에 저장해 둔다.


그러고선 SESSION ID 를 지워보자



브라우저를 리플래쉬하면??




당연히 세션ID를 잃어버렸으니까 서버에서 자기 세션을 찾지 못하므로 서버는 이놈이 로그인 안된줄 알고 로그인 하라는 메시지를 보낼 것이다.


그렇다면 다시 아까 메모장에 저장해둔 세션 아이디를 입력하고 리프래쉬 해보자!


어떻게 되는가?




멀쩡하게 로그인 한것과 같은 화면이 되버린다.


아직 서버 안에 세션객체가 죽지 않았기 때문에


알맞는 세션 아이디만 있으면 다시 그 세션(로그인 정보와 장바구니 정보)를 물려줄 것 이다.



이쯤되면 아~ 로그아웃을 꼭 누르고 나와야 겠구나 할것이다.


왠만한 사이트의 로그아웃 기능은 쿠키의 세션아이디를 지워버리는 역할을 한다.


만약 피씨방 같은곳에서 로그아웃을 하지 않고 브라우저 창만 닫고 나온다면?


 


피씨에 저장된 쿠키의 세션 아이디값을 읽어다가 


브라우저에 쿠키를 설정하고 해당 사이트에 들어가면 그대로 로그인이 되버리므로 조심 조심


공공장소에서는 항상 로그아웃버튼을 누르고 나오시길!



[추가정리]
1. 쿠키
- 서버에 접속시 접속한 클라이언트 정보를 클라이언트에 저장한다.
-이후에 서버로 전송되는 요청에는 쿠키가 가지고 있는 정보가 같이 포함되어서 전송
-크기는 4096 Byte 이하
-클라이언트에 총 300개 까지 쿠키를 저장할 수 있다.
-하나의 도메인 당 20개의 값만 가질수 있다.
-쿠키는 이름,값,유효기간,도메인,경로 등으로 구성

사용예)
방문했던 사이트에 다시 방문 하였을 때 아이디와 비밀번호 자동 입력
팝업에서 오늘 이창을 다시 보지 않음체크

2. 세션
-웹서버 쪽 웹 컨테이너에 상태를 유지하기 위한 정보를 저장
-저장 데이터에 제한이 없음
-웹서버는 각각의 웹브라우저로부터 발생한 요청에 대하여 특정한 식별자를 우여하여 같은 브라우저인지 구별
-세션쿠키라고도 한다.
-브라우저를 닫거나, 서버에서 이 이쿠키를 삭제 했을때만 삭제가 되므로 비교적 지속성이 쿠키보다 안전하다
- 각각의 클라이언트마다 고유의 ID를 부여
-세션 객체마다 저장해 둔 데이터를 이용하여 서로 다른 클라이언트의 요구에 맞게 서비스제공

차이점은 : 
저장되는 곳이 다르다는 것.
= (쿠키는 클라이언트, 세션은 서버)
저장형태 
=(쿠키 텍스트형식, 세션은 Object형으로 메모리에 저장)
만료기간도 다르다는 것
= (쿠키는 저장시 설정하는데 설정하지 않으면 브라우저 종료시 소멸, 세션은 정확한 시점을 알 수 없음)
자원
=(쿠키는 클라이언트자원사용, 세션은 서버의 자원 사용)
용량
=(쿠키는 한 도메인에 20개, 쿠키당 4KB, 총 300개, 세션은 서버가 허용하는 한 용량에 제한 없음)

출저: 

 http://jhoonslife.tistory.com/354

http://marcof.tistory.com/16

http://bllizz.tistory.com/15

Posted by hoonihoon
2013. 12. 2. 12:51

어디서?

미래창조과학부에서 2013년말까지 010번호로 변경 해야 한다고 발표.

왜?

미래창조과학부에서는 010통합정책 목표를 다음과 같이 설정했다.


기대효과?


010 가입자끼리는 010을 누르지 않아도 전화가 걸린다는 사실을 알고 계신가요?

010 가입자끼리 전화를 걸때 아직도 010 식별번호를 누르는 사람이 많아요. 

010 번호를 사용하는 사람끼리는 통신사에 관계없이 전화번호만 누르면 전화가 되요^^













Posted by hoonihoon
2013. 11. 29. 16:29

[a.html] => 이동 전 페이지

<HTML>
<HEAD>
<TITLE>Get 방식의 주소 파라미터를 받아서 처리</TITLE>
<script type="text/javascript">
var move = function(number) {
location.href = "b.html?tab=" + number;
};

</script>
</HEAD>

<BODY>
<input type="button" value="1번 탭으로 이동" onClick="move(1)" />
<br />
<input type="button" value="2번 탭으로 이동" onClick="move(2)" />
</BODY>
</HTML>



[b.html] => a.html에서 이동할 페이지

<HTML>
<HEAD>
<TITLE>Get 방식의 주소 파라미터를 받아서 처리</TITLE>

<script type="text/javascript" src="jquery-1.6.4.js"></script>

<script type="text/javascript">
var displayTab = function(number) {
$('div > div').css('display', 'none');
$('#tab_' + number).css('display', 'block');
};

$(document).ready(function() {
var address = unescape(location.href);
var param = "";
if(address.indexOf("tab", 0) != -1) {
param = address.substring(address.indexOf("tab", 0) + 4);
} else {
param = "1";
}
displayTab(param);
});

</script>

<style type="text/css">
div#tab_1 {display:none; position:absolute; top:50px; left:50px; width:100px; height:100px; background:red;}
div#tab_2 {display:none; position:absolute; top:150px; left:50px; width:200px; height:200px; background:blue;}
</style>

</HEAD>

<BODY>
<div id="tab_menu">
<input type="button" value="1번 탭 보이기" onClick="displayTab(1)" />
<input type="button" value="2번 탭 보이기" onClick="displayTab(2)" />
</div>
<div id="tab">
<div id="tab_1">
</div>

<div id="tab_2">
</div>
</div>
</BODY>
</HTML>


[실행화면]

a.html


1번 버튼으로 이동


2번 버튼으로 이동



잡다한 내용은 다 빼고.. 여기서 중요한 것은 unescape(location.href) 이 부분이다
저 구문을 출력해 보면 아래와 같이 나온다


현재 주소창에 띄워져 있는 문자들을 그대로 가져온다

그런데 여기서 필요한것은 tab = 2 라는 부분이므로 

var address = unescape(location.href);
var param = "";
if(address.indexOf("tab", 0) != -1) {
        param = address.substring(address.indexOf("tab", 0) + 4);
} else {
        param = "1";
}


이런식으로 "2" 를 가져올수 있다

그냥 써줘도 되는데 굳이 조건식을 사용한 것은.. 
a.html을 거쳐서 b.html로 갈수도 있지만 바로 b.html을 실행했을때는 1번 페이지를 보여주려고 했기 때문이다


출저 : http://ggoreb.tistory.com/162

Posted by hoonihoon
2013. 11. 26. 10:11

* 이번에는 기존 안드로이드 개발자들도 다소 생소할 Fragment에 대해서 공부해보자. Fragment는 디바이스가 워낙에 다양해졌기 때문에 작은 모바일 화면, 중간 사이즈 모바일, 그리고 태블릿까지 지원해주기 위하여 코드 재사용성을 높여주기 위한 새로운 개발 디자인이기 때문에 기존의 Activity 기반의 화면 구성보다 더 발전되고 호환성이 있는 디자인을 꾸밀 수가 있게 됐다.


- 이전 글

2012/11/07 - [Android(안드로이드) 앱 개발 기초] 안드로이드 프로젝트 생성하고 에뮬레이터로 앱 실행하기

2012/11/10 - [Android(안드로이드) 앱 개발 기초] 간단한 인터페이스 구현과 다른 Activity로 넘어가기

2012/11/21 - [Android(안드로이드) 앱 개발 기초] Activity 라이프사이클 공부

2012/11/24 - [Android(안드로이드) 앱 개발 응용] Google Map API로 지도 보여주기(MapView), Overlay Item 그려주기 예제

2012/11/28 - [Android(안드로이드) 앱 개발 응용] Location GPS 위치 가져오기 및 최적화

2012/12/05 - [Android(안드로이드) 앱 개발 기초] 런타임 설정(로테이션, orientation) 변환 라이프사이클

2012/12/19 - [Android(안드로이드) 앱 개발 응용] 쉽게 Google Map 위에 말풍선 띄우기


* Fragment

: Fragment는 Activity 내부에서 UI나 프로세스를 정의할 수 있는 더 작은 단위이고, 하나의 Activity에 다수의 Fragment를 배치하거나 여러 개의 Activity에서 하나의 Fragment를 재사용하는 것이 가능하다. 따라서 Fragment는 Activity를 구성하는 작은 모듈이라고 생각하면 되며, Activity와는 별도의 라이프사이클과 이벤트 또한 따로 처리가 가능하고, 하나의 Activity가 실행되고 있을 때 유동적으로 추가하고 삭제가 가능하도록 되어있다.


: Fragment는 반드시 Activity에 포함되어있어야하며, 포함된 Activity에 의해 Fragment 만의 라이프사이클이 진행되고 해당 Activity의 라이프사이클에도 직접적으로 영향을 받도록 되어있다. 예를 들면, Activity가 pause 단계에 들어가면 하위 Fragment들도 모두 pause가 되고, Activity가 destroy 되면 하위 Fragment 또한 전부다 destroy 된다. 어찌되었든, Activity가 running 상태를 유지하고 있을 때 하위 Fragment들을 독립적으로 추가 삭제하는 등 다룰수 있고, 그러한 동작을 했을 때에는 백스택에 들어가게 되며, 사용자가 백버튼을 누르게 되면 이 백스택에 있는 동작들을 취소할 수 있도록 지원해준다.


: Activity에 Fragment를 추가할 때에는 Activity 안에 ViewGroup를 추가하여 이 안에 설정을 하고 Fragment 자체에도 별도의 레이아웃을 만들어서 저장하면 된다. Fragment를 Activity의 레이아웃 xml에 추가할 때에는 <fragment> 태그를 사용하고, 소스단에서 추가를 하고 싶다면 이미 존재하는 ViewGroup 에 추가를 하면 된다.  하지만 Fragment를 사용할 때에는 반드시 Activity의 레이아웃에 추가할 필요는 없고 별도의 UI 없이 Activity를 위한 프로세스 처리를 담당할 수도 있다.


: 그럼 이제부터 Fragment를 이용하여 앱을 만들고, Activity와는 다른 Fragment의 상태들에 대하여 알아보고, Activity의 백스택에 추가할 때 상태를 유지하는 방법, 그리고 이벤트를 Activity나 다른 Fragment와 공유하는 방법 등을 알아보자.



* 디자인 철학

: Fragment는 허니콤(안드로이드 3.0, API level 11)에서부터 태블릿을 위한 UI 개발 방법 디자인으로 소개되었고 다양한 사이즈의 화면에 대하여 더 많은 UI를 보여주거나 일을 처리할 수 있도록 앱의 UI 디자인을 도와준다. Fragment는 Activity의 레이아웃을 부분부분 나눠서 개발자가 다른 크기의 화면에서 개발할 때 View의 구조를 복잡하게 수정하지 않아도 쉽게 재사용할 수 있도록 도와준다. 이렇게 Activity를 여러 모듈들로 나눔으로써 Activity는 런타임 동안 레이아웃을 유동적으로 바꿀수도 있고 백스택에 그 변화를 저장하여 백 버튼과의 연동 또한 쉽게할 수 있게 된다.


: 예를 들면, 메일을 읽는 앱이 있다고 할때 Fragment 하나는 메일 목록을 나타내는 Fragment로 설정하고, 메일의 내용을 보여주는 화면을 또 다른 Fragment로 설정했을 때에 태블릿에서는 큰 화면에서 하나의 Activity 안에 왼쪽에 뉴스의 목록을 보여주는 Fragment를 띄워주고, 오른쪽에는 메일의 내용을 보여주는 식으로 화면을 구성해서 각 Fragment가 독립적인 사용자 입력과 라이프사이클을 가질 수 있도록 구성하여 사용자가 큰 화면에서는 하나의 Activity에서 화면의 전환없이 메일을 선택하고 읽을 수 있도록 해줄 수 있다.


: 이를 모바일로 옮길 때 각 목록을 보여주는 Fragment와 내용을 보여주는 Fragment를 재사용하여 서로 다른 Activity에 넣어서 작은 화면에서는 목록을 선택하는 화면 따로, 내용을 보여주는 화면을 따로 설정할 수 있도록 할 수 있는 것이다. 이것은 Fragment 하나가 여러 개의 다른 Activity에 포함될 수 있기 때문에 화면 사이즈에 따라 다른 Activity를 사용하면서 같은 Fragment를 이용하고 Fragment들을 다르게 조합하여 UX를 최적화할 수 있도록 도와주는 것이다. 이렇게 큰 화면에서는 한 화면에 전부 포함했지만, 작은 화면에서는 기능을 나눠야할 때가 있을 때 Fragment를 활용하면 편리하다.






* Fragment 만들기

:Fragment를 만들기 위해서는 Fragment 클래스를 확장해야한다. Fragment의 소스코드는 Activity와 상당히 비슷하게 구성되어있는데 onCreate(), onStart(), onPause()등과 같이 Activity와 비슷한 함수들도 포함되어있다. 따라서 기존의 안드로이드 앱을 Fragment 기반으로 수정하고 싶다면 Activity의 이벤트 콜백 함수들을 조금만 수정할 수 있다. 일반적으로 Activity와 비슷하게 다음의 라이프사이클을 가지게 된다.


  • onCreate(): 시스템이 Fragment를 처음에 만들 때 호출한다. Fragment 안에서 onPause나 onStop 단계에 들어갔다가 다시 resume 할 때에도 계속 유지될 중요한 정보나 요소들은 이 단계에서 초기화를 하면 된다.
  • onCreateView(): Fragment가 UI를 처음으로 그리고자할 때에 호출된다. Fragment를 통해 UI를 그리고자 한다면 이 함수의 결과로 Fragment layout의 루트에 해당하는 View를 리턴해야한다. 만약 null을 리턴한다면 Fragment는 UI를 제공하지 않는다.
  • onPause(): 시스템이 사용자가 Fragment를 떠날 때에 호출된다. 이는 Fragment가 반드시 없어지는 것을 뜻하지는 않으며, 이 콜백 함수 안에서 사용자가 다시 돌아오지 않았을 경우 유지시켜줄 데이터들을 저장하는 과정을 구현하는 것이 좋다.

: 모든 앱은 최소한 위의 3가지 콜백 함수를 모든 Fragment마다 기본적으로 구현해야 하며, 다른 시기에 호출되는 다른 콜백함수들을 사용함으로써 다양하게 Fragment의 라이프사이클을 조율할 수 있다.




: 기본 Fragment 이외에도 이를 확장하고 있는 보조적인 역할들을 하는 Fragment들이 있다.

  • DialogFragment: 떠다니는 다이얼로그를 보여주는 Fragment. Fragment는 백스택에 넣어둘 수 있기 때문에 사용자가 다시 Fragment로 복귀하고자 할 때에 Activity에 기본적으로 들어있는 다이얼로그 대신에 사용할수 있는 좋은 대체제이다. 
  • ListFragment: Adapter를 통해서 List를 보여주는 Fragment로 ListActivity와 비슷하고, list view에서 다룰 수 있는 onListItemClick()과 같은 콜백 함수들도 제공한다.
  • PreferenceFragment: Preference 객체들을 목록으로 보여주는 PreferenceActivity와 비슷하며, 앱의 Settings를 만들 때에 유용하게 사용할 수 있다.


* UI 추가하기

: Fragment의 UI는 Activity의 UI의 일부로 제공되며 Fragment만의 레이아웃 xml을 보유한다. Fragment를 통해서 레이아웃을 제공하고자한다면, 안드로이드 시스템이 Fragment의 레이아웃을 그릴 때 호출하는 onCreateView() 콜백 함수를 구현해야한다. 이 함수에서 Fragment의 레이아웃의 루트 View를 리턴하면 된다. 만약에 ListFragment를 이용하고 있다면, 기본적으로 ListView를 리턴하기 때문에 별도로 구현할 필요는 없다.


: onCreateView()에서 레이아웃을 리턴하기 위해서는 XML로 레이아웃을 생성하여 inflater를 이용하여 View를 생성하면 되는데, onCreateView() 함수에서는 기본적으로 인자에서 LayoutInflater를 넘겨주기 때문에 이것을 이용하여 View를 생성하면 된다.


1
2
3
4
5
6
public static class ExampleFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.example_fragment, container, false);
    }
}


: 여기서 container 인자는 해당 Fragment를 삽입할 부모 Activity에서 온 ViewGroup을 넘겨주고, savedInstanceState 인자는 이전의 상태에 대한 데이터를 포함하여 인자로 넘겨주게 된다. 그리고 inflate 함수는 3개의 인자를 받게 되는데 아래와 같다.

  • inflate를 실행할 레이아웃의 리소스 ID
  • inflate한 레이아웃의 부모가 될 ViewGroup. 인자로 받았던 container를 넘겨주는 것은 시스템에서 inflate한 레이아웃을 어디에 그려야할지 정해줘야하는 중요한 단계이다.
  • 세번째는 inflate한 레이아웃을 두번째 인자에 추가할 것인지 여부를 묻는 인자로, 시스템에서 자동으로 inflate한 내용을 container에 추가하게 되므로 false를 넘겨줘야 중복되게 추가하는 일이 없다.


* Fragment를 Activity에 추가하기

: Fragment는 일반적으로 Activity의 전체 UI 중 일부를 표현하게 된다. 따라서 2가지 방법으로 Activity의 레이아웃에 추가할 수 있다.


  • Fragment를 Activity의 레이아웃 XML 파일에 추가

: 2개의 Fragment 를 나란히 붙이고자한다면 아래와 같은 구조의 XML 레이아웃을 가지면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <fragment android:name="com.example.mail.MailListFragment"
            android:id="@+id/list"
            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
    <fragment android:name="com.example.mail.MailContentsFragment"
            android:id="@+id/viewer"
            android:layout_weight="2"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
</LinearLayout>


: 여기서 <fragment> 태그의 android:name 속성은 위의 레이아웃이 사용할 Fragment 클래스를 나타내는데, 시스템에서 Activity의 레이아웃을 생성할 때에 위의 android:name에 명시된 Fragment 클래스의 인스턴스를 생성하여 onCreateView를 호출하여 바로 <fragment>의 위치에 넣게 된다. 위와 같이 레이아웃을 통해 Fragment를 선언하는 경우 Fragment를 다시 시작하기 위해서는 아이디를 부여해줘야하는데 일반적으로 3가지 방법이 있다.


  1. android:id 속성에 ID 추가
  2. android:tag 에 스트링을 ID로 추가
  3. 위의 두가지를 사용하지 않는다면 부모 View의 ID를 사용하게 된다.


  • 프로그래밍으로 Fragment를 추가하기

: Activity가 실행되고 있는 동안에도 Fragment를 Activity의 레이아웃에 추가하는 것이 가능하다. 이 때에는 단순히 ViewGroup만 있으면 되며, Activity로부터 FragmentTransaction을 가져와서 아래와 같이 활용할 수 있다.

1
2
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
: 그리고 fragmentTransaction의 add 함수와 commit 함수를 통해서 view에 Fragment를 추가할 수 있다.

1
2
3
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();


: 여기서 fragmentTransaction.add() 함수에 첫번째 인자로 해당 Fragment가 위치될 ViewGroup의 리소스 ID를 넘겨주고, 두번째 인자로 추가할 Fragment를 넣으면 된다. fragmentTransaction에 Fragment를 추가하거나 변화를 주었을 경우에는 반드시 commit()함수를 실행해야 변화가 적용된다.



* UI 없이 Fragment 추가하기

: 위에는 Activity에 UI로서 일부 포함되는 Fragment를 보여줬다면 이번에는 UI를 제공하지 않고 백그라운드에서 동작하는 Fragment를 추가하는 방법을 살펴보자.


: UI가 없는 Fragment를 추가하기 위해서는 View에 설정하는 ID가 없기 때문에 tag를 통해 설정한 ID를 통해서 Fragment를 찾아서 추가해야하는데, 이때에 FragmentTransaction.add(Fragment, String) 함수를 사용하면 된다. 이 때 두번째 인자는 Fragment의 tag로 고유한 스트링 ID를 부여하면 된다. 이 함수를 활용해서 Fragment를 추가하는 경우 Activity의 View와 연동되지 않았기 때문에 Fragment의 onCreateView 함수가 호출되지 않으므로 이 함수를 구현할 필요가 없다.


: tag를 이용한 ID의 할당은 UI가 없는 Fragment에 제한되지 않고 UI가 있어도 tag를 통한 스트링 ID를 부여할 수도 있지만 UI가 없다면 오로지 tag를 이용해서 스트링 ID를 할당해야 Activity 로부터 Fragment를 찾을 때 findFragmentByTag() 함수를 활용할 수 있다.



* Fragment를 관리하기

: Activity 안에서 Fragment를 관리하기 위해서는 FragmentManager를 활용해야하는데, Activity 안에서 getFragmentActivity() 함수를 호출함으로써 얻어올 수 있다. 이 FragmentManager를 통해서 할 수 있는 것들은 아래와 같다.


  • Activity 안에 존재하고 있는 Fragment를 findFragmentById()나 findFragmentByTag()를 통해서 검색
  • 사용자가 백버튼을 누른것과 같은 효과로 popBackStack()를 활용하여 백스택에 들어있는 Fragment를 팝
  • addOnBackStackListener()를 통해 백스택이 바뀌었을 때 호출되는 콜백 리스너 등록

: 그리고 또 위의 예에서 쓰였던 FragmentTransacttion을 가져와서 Fragment들을 추가하거나 삭제할 수 있는 트랜젝션을 생성하는데 사용된다.



* Fragment 트랜젝션 사용하기

: Fragment의 가장 큰 장점은 바로 현재의 Activity 안에서 추가하고 삭제하고 교체하면서 다른 여러 가지 동작들을 함께 실행할 수 있다는 것이다. Activity에 행하는 변화들은 트랜젝션으로 불리우며, API에서는 FragmentTransaction을 활용하여 구현할 수 있다. 또한 각 트랜젝션을 백스택에 저장하여 사용자가 백버튼을 눌렀을 때 변화되었던 내용을 다시 원복하는 것도 가능하다. 이러한 FragmentTransaction은 FragmentManager를 통해서 얻을 수 있다.


1
2
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();


: 여기서 하나의 트랜젝션에는 동시에 실행되길 원하는 동작들을 추가하여 사용하면 되고, 이러한 동작들은 add() 함수, remove() 함수, replace() 함수를 통해서 설정할 수 있다.그리고 모든 동작들을 추가하고나면 commit() 함수를 통해서 동시에 실행하게 된다. 이렇게 commit()을 하기 전에는 addToBackStack()함수를 호출하여 이전 상태를 백스택에 추가하여 사용자가 백버튼을 눌렀을 때에 대한 호환성을 추가할수도 있다. 백스택에 추가하여 이전 상태를 저장하는 예는 아래와 같다.

1
2
3
4
5
6
7
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
 
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
 
transaction.commit();


: 위의 예에서 새로운 newFragment를 만들고, 현재 R.id.fragment_conatiner의 아이디를 가지고 있는 레이아웃에 있는 Fragment를 교체하게 되는 것이다. 그리고 addToBackStack()함수를 호출함으로써 교체된 Fragment는 백스택에 저장되어 사용자가 다시 이전의 상태로 가고자할 때를 대비해서 보존하게 된다. 하나의 트랜젝션 안에서 add()나 remove() 등을 이용해서 여러 개의 변화를 한꺼번에 addToBackStack()에 저장하고 commit()하게 사용자가 백버튼을 눌렀을 때에도 한꺼번에 다시 원래대로 돌아오게 된다. 만약 Fragment가 바뀌는 애니메이션을 원한다면 commit() 하기 전에 setTransition() 함수로 애니메이션을 설정할 수도 있다.


: commit()을 호출하는 것은 바로 트랜젝션을 씰행한다기보다는 Activity의 UI 스레드에 추가하여서 실행가능할 때에 실행이 되는데, 만약 어떠한 Fragment가 다른 Fragment가 반드시 먼저 실행되어야한다면 스레드에 대기중인 트랜젝션을 executePendingTransactions() 함수를 통해서 바로 실행할수가 있다. 하지만 이 함수를 호출해야할 필요성이 있는 것은 위와 같이 의존성이 있을 때에만 해당된다. commit()을 할 때의 주의점이라면 호출하는 시점은 Activity가 상태를 저장하기 전에 이루어져야한다. 만약 Activity의 onSaveInstanceState 콜백함수가 호출되고나서 commit을 하고자한다면 예외 상황이 발생할 것이고, 만약 이 상태 이후에 commit()을 하고 싶다면 commitAllowingStateLoss()를 이용하여 commit하면 된다.



* Acitivty와 의사소통하기

: Fragment는 Activity와는 별개로 독립적인 모듈로 구현이 되지만, 다양한 Activity 안에서 활용될 수 있기 때문에 현재 포함된 Activity와 연결되어야할 필요성이 있다. 따라서 만약 Activity의 객체에 접근을 하고 싶다면, Fragment 클래스의 getActivity() 함수를 이용하면 Activity의 함수들을 활용할 수 있따.


1
View listView = getActivity().findViewById(R.id.list);


: 반대로 Activity에서 Fragment에 접근을 하고자 한다면, FragmentManager의 findFragmentById() 함수나 findFragmentByTag()를 통해서 Fragment를 검색하여 접근하면 된다.


1
ExampleFragment fragment - (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);



* Activity에 이벤트 콜백을 설정하기

: Fragment에서는 Activity와 이벤트를 공유하고자할 때에는 콜백 인터페이스를 정의하여 Activity에서 이벤트 콜백의 구현을 요청할 수 있다. 만약 Acitivty가 콜백을 Fragment를 통해서 받게 되면 다른 Fragment로부터 정보를 받아서 공유를 할 수 있게 된다. 예를 든다면 메일 앱에서 2개의 Fragment가  Activity에 포함되고 있을 때에 Fragment A에서 목록을 보여주고, Fragment B에서는 그 내용을 보여준다고 할 때에 Fragment A에서 아이템을 선택했을 때 그 정보를 Fragment B에 넘겨줘야하는데 이럴 때에 아이템을 선택하는 이벤트의 콜백을 Activity에 설정하여 Activity에서 Fragment B 로 변화를 요청하는 구조를 가지면 된다. 예를 들면, 이럴 때에는 Fragment A 안에 OnArticleSelectedListener 인터페이스를 구현하여 활용하면 된다.


1
2
3
4
5
public static classFragmentA extends ListFragment {
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }
}


: 이렇게 Fragment 에서 인터페이스를 정의하면 이 Fragment를 포함하고 있는 Activity는 이 인터페이스를 구현하여 onArticleSelected 함수를 구현하여 Fragment의 onAttach 콜백함수에서 리스너를 설정하면 된다. 이 onAttach 함수는 Fragment가 Activity에 추가 될 때 호출되는 콜백 함수이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
public static class FragmentA extends ListFragment {
    OnArticleSelectedListener mListener;
 
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnArticleSelctedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener)l
        };
    }
}


: 이렇게 Fragment가 추가되었을 때에 추가된 Activity를 리스너로 추가하면서 만약 Activity가 OnArticleSelectedListener를 구현 안했으면 예외를 발생시키게 된다. 이제 Fragment에서 이벤트가 발생하게 되면 멤버 변수로 설정된 Activity의 onArticleSelected함수를 호출하여 다른 Fragment와 정보를 공유하면 된다. 이제 ListFragment에 들어있는 onListItemClick 이벤트 콜백 함수에 해당하는 mListener의 onArticleSelected 함수를 호출하는 부분을 구현하면 된다.

1
2
3
4
5
6
7
8
9
public static class FragmentA extends ListFragment {
    OnArticleSelectedListener mListener;
 
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
        mListener.onArticleSelected(noteUri);
    }
}

: onListItemClick에서 넘어오는 id를 기반으로 클릭된 아이템의 id를 가져와서 ContentProvider를 통해서 해당하는 데이터를 요청할 수 있도록 uri를 넘겨주고 있다. uri를 생성하는 부분은 content provider에 대하여 공부하면 알 수 있다.



* 액션바에 아이템 추가하기

: Fragment는 또한 onCreateOptionsMenu()를 구현함으로써 옵션 메뉴와 액션바 메뉴의 아이템에 영향을 미칠수 있다. 이 함수가 호출을 받으려면 일단 setHasOptionsMenu()를 onCreate() 함수 안에서 호출하여 현재의 Fragment가 옵션메뉴에 항목을 추가하겠다는 것을 명시해야한다. 만약 명시를 안한다면, onCreateOptionsMenu() 함수는 호출되지 않을 것이다.


: 옵션메뉴에 추가한 메뉴 아이템들은 현재의 메뉴 아이템의 뒤에 추가될 것이고, 또한 Fragment에서는 옵션 메뉴가 선택되었을 때 onOptionsItemSelected() 콜백 함수가 호출된다. 또한 Fragment 레이아웃에서 컨텍스트 메뉴를 설정할수도 있는데, registerForContextMenu() 함수를 호출하게 되면 사용자가 컨텍스트 메뉴를 열 때에 onCreateContextMenu()의 콜백 함수가 호출되고, 사용자가 컨텍스트메뉴의 항목을 선택하게 되면 onContextItemSelected() 콜백 함수가 호출된다.


: 위처럼 Fragment에서 컨텍스트메뉴나 메뉴 아이템에 대한 콜백 함수를 설정하지만 우선적으로 Activity에 있는 콜백함수가 호출되며, 만약 Activity에서 해당하는 메뉴 아이템을 다루지 않는다면, 이벤트는 Fragment를 통해서 호출될 것이다.



* Fragment의 라이프 사이클

: Fragment의 라이프사이클을 다루는 것은 Activity의 라이프사이클을 다루는 것과 비슷하다. Activity와 같이 Fragment에서는 3가지의 상태를 가지고 있다.

  • Resumed: 현재 Fragment가 실행되고 있는 Activity에 있다.
  • Paused: 다른 Activity가 전면에 나와서 포커스를 가지고 있지만, 현재 Fragment가 포함된 Activity는 아직은 일부가 보이고 있다. 앞에 있는 Activity는 반투명하거나 전체 화면을 덮고 있지 않다.
  • Stopped: 현재 Fragment가 보이지 않는다. 현재 Activity가 멈추었거나, Fragment가 Activity에서 삭제되었지만 아직 백스택에 있는 경우에 이 상태에 들어간다. 이렇게 Stopped 상태에 있는 Fragment는 아직 메모리에 남아있으며 모든 상태와 멤버 정보는 시스템에 의하여 보존되지만 현재 사용자에게는 보이지 않고 Activity가 destroy될 때 Fragment도 같이 destroy된다.

: Activity와 같이 Fragment의 상태를 저장하기 위하여 Bundle을 이용하여 onSaveInstanceState 함수 안에서 저장하고,, Activity의 프로세스가 Fragment를 삭제하거나 Activity가 멈췄다가 다시 생성되었을 때 Fragment의 상태를 되돌리고자할 떄에 onCreate()나 onCreateView(), onActivityCreated() 함수 안에서 다시 상태를 재생하면 된다. 


: Fragment의 라이프사이클이 Activity와 가장 다른 점은 바로 백스택에 저장되고 상태를 유지해야한다는 점이다. Activity들은 시스템에서 자동적으로 백스택에 넣어지는 반면에 Fragment는 트랜젝션을 통해서 Activity에서 Fragment를 백스택에 넣으라고 addToBackStack() 함수를 통해 요청을 해야 들어가게 된다. 이외에는 Fragment의 라이프사이클은 Activity의 라이프사이클과 매우 비슷하여 관리하기 편리하다. 따라서 Activity의 라이프사이클을 관리하는 방법을 그대로 Fragment에 적용하면 하면 되고, Activity의 라이프사이클이 Fragment의 라이프사이클에 어떻게 영향을 미치는지 파악하면 좋다.



* Activity의 라이프사이클와 조율하기

: Activity의 사이프사이클은 바로 Fragment의 라이프사이클에 영향을 미치게 되며 Activity의 라이프사이클 콜백함수가 호출 될 때에 그에 해당하는 Fragment의 라이프사이클 콜백 함수도 호출된다. 예를 들면, Activity의 onPause가 호출될 때 Fragment의 onPause 또한 호출된다.


: 이외에도 Fragment에서는 몇가지 추가적인 라이프사이클 콜백 함수들이 있어서 Activity 와의 상호작용을 용이하게 도와주고 있다. 

  • onAttach(): Activity에 할당되었을 때 호출되며 인자로 Activity를 넘겨준다.
  • onCreateView(): Fragment에 할당된 View를 생성하고자 할 때 호출된다.
  • onActivityCreated(): Activity의 onCreate() 함수가 호출되고나서 호출된다.
  • onDestroyView(): Fragment의 View가 제거될 때 호출된다.
  • onDetach(): Fragment가 Activity로부터 할당이 제거될 때 호출된다.

: Fragment의 라이프사이클은 Activity의 라이프사이클에 따라서 아래의 그림과 같이 매핑되어 있다.



: 여기 나와있는 것은 Activity의 각 상태가 성공적으로 전이가 되었을 때, Fragment가 호출 받을 수 있는 콜백 함수의 목록이다. 예를 들면, Activity가 onCreate()의 호출을 받으면, Fragment는 onActivityCreated() 함수까지만 진행이 가능한 것이다. 그리고 한번 Activity가 resumed 상태에 도착하게 되면, Fragment를 자유롭게 추가하고 삭제를 하게 되는데, 이 때에만 Fragment는 라이프사이클의 변화가 자유롭게 일어날 수 있게 되는 것이다. 어찌되었든, Activity가 resumed 상태를 벗어날 때, Fragment는 또 다시 Activity의 라이프사이클에 의존적으로 라이프사이클을 진행하게 된다.



* Fragment 예

: 이번에는 위에서 예를 들었던 목록과 내용을 볼 수 있는 2개의 Fragment를 사용하는 Activity의 예를 살펴보자. 아래의 Activity는 하나의 Fragment에서는 글 목록을 보여주고 다른 하나는 선택된 아이템의 상세 정보를 표시해주는 역할을 하게 된다. 또한 화면 설정에 따라 다른 환경 설정을 하는 것도 살펴보자. 일단 Activity에서는 onCreate()안에서 레이아웃을 설정한다.


1
2
3
4
5
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.fragment_layout);
}


: 그리고 위에서 사용하고 있는 fragment_layout.xml은 아래와 같이 구성한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
     
    <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
        android:id="@+id/titles"
        android:layout_weight="1"
        android:layout_width="0px"
        android:layout_height="match_parent" />
    <FrameLayout android:id="@+id/details"
        android:layout_weight="1"
        android:layout_width="0px"
        android:layout_height="match_parent"
        android:background="?android:attr/detailsElementBackground" />
</LinearLayout>


: 위와 같이 시스템은 TitlesFragment을 초기화하고 곧 Activity에서는 우측의 FrameLayout을 초기화하여 보여줄 것이지만, 처음에는 빈 공간으로 남겨져 있고 사용자가 선택을 할 때에 내용이 나타날 것이다.  어찌 되었든, 위와 같은 경우는 단말이 가로로 있을 때 목록과 내용을 한 화면에서 편하게 보여줄 수 있으므로 가로 방향의 레이아웃으로 활용하기 위하여 res/layout-land/fragment_layout.xml 에 저장을 하도록 하자. 그리고 화면이 세로가 되었을 때에는 res/layout/fragment_layout.xml을 아래와 같이 작성하면 된다.


1
2
3
4
5
6
7
8
9
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
 
    <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragmenttt"
        android:id="@+id/titles"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</FrameLayout>

: 이렇게 세로로 된 경우에는 오로지 TitlesFragment만을 표시해주게 되고, 사용자가 목록 아이템을 누르면 앱에서는 2번째 Fragment를 바로 실행하기보다는 새로운 Activity를 시작하여 해당하는 Fragment를 시작하게 될 것이다. 다음에는 TitleFragment가 ListFragment를 확장하면서 타이틀 목록을 가져오는 것을 구현해보자. 아래의 소스에서 볼 수 있듯이 사용자가 아이템을 클릭했을 때에 2가지 행동이 가능하다. 화면이 가로로 있을 때에는 새로운 Fragment를 그려주거나 화면이 세로로 되어있을 때에는 새로운 Activity를 추가하는 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public static class TitlesFragment extends ListFragment {
    boolean mDualPane;
    int mCurCheckPosition = 0;
 
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        setListAdapter(new ArrayAdapter<string>(getActivity(), android.R.layout.simple_list_item_activated_1, ShakeSpeare.TITLES));
        View detailsFrame = getActivity().findViewById(R.id.details);
        mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;
 
        if (savedInstanceState != null) {
            mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
        }
        if (mDualPane) {
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE)l
            showDetails(mCurCheckPosition);
        }
    }
 
    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("curChoice", mCurCheckPosition);
    }
 
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        showDetails(position);
    }
 
    void showDetails(int index) {
        mCurCheckPosition = index;
        if (mDualPane) {
            getListView().setItemChecked(index, true);
            DetailsFragment details = (DetailsFragment) getFragmentManager().findFragmentById(R.id.details);
            if (details == null || details.getShownIndex() != index) {
                details = DetailsFragment.newInstance(index);
                FragmentTransaction ft = getFragmentManager().beginTransaction();
                if (index == 0) {
                    ft.replace(R.id.details, details);
                } else {
                    ft.replace(R.id.a_item, 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);
        }
    }
}
</string>


: 그리고 이번에는 두번째 Fragment인 DetailsFragment를 구현해보면, 현재 목록에서 선택된 아이템의 상세한 내용을 보여주도록 하는 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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;
    }
 
    public int getShownIndex() {
        return getArguments().getInt("index", 0);
    }
 
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if (container == null) {
            return null;
        }
 
        ScrollView scoller = 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[getShownIndex()]);
        return scroller;
    }
}


: 이제 다시 이전에 구현했던 2번째 동작인 새로운 Activity를 열어서 별도의 화면으로 보여줄 때에 필요한 Activity를 구현해보면 아래와 같이할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static class DetailsActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
            finish();
            return;
        }
 
        if (savedInstanceState == null) {
            DetailsFragment details = new DetailsFragment();
            details.setArguments(getIntent().getExtras());
            getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
        }
    }
}


: 위의 소스를 보면, 현재 Activity는 환경설정이 화면이 가로로 되어있으면 종료하고 상위의 Activity가 Fragment를 다루도록 하고 있는데, 이것은 바로 화면이 세로로 들어왔다가 사용자가 화면을 가로로 회전시키게 되면 현재 Activity를 종료하고 상위의 Activity에서 다루도록 하고 있는 것이다.



원문: http://developer.android.com/guide/components/fragments.html


끝.

출저: http://unikys.tistory.com/318

Posted by hoonihoon
2013. 11. 25. 10:25
가끔씩 마주치게 되는 "OutOfMemoryError : bitmap size exceeds VM budget" 에러는 메모리 누수가 주요 원인입니다. 이와 관련된 링크를 모아봤습니다.

* 액티비티가 멈출 때 비트맵을 재활용(즉 GC)되게 하라

- bitmap 이미지인 경우 recycle() 호출
- onPause에서 수행하는게 좋음
- ((BitmapDrawable)imageView.getDrawable()).getBitmap().recycle();

* 이미지를 미리 줄여서 읽어들여라

- BitmapFactory.Options.inSampleSize 활용

* Activity Context에 대한 참조(reference)를 오랫동안 유지하지 말아라

- Drawable.setCallback(null) 사용
- WeakReference를 가진 static 내부 클래스
- 이미지를 static 변수로 처리하지 마라

* 외부(outer) 클래스의 상태에 의존하지 않는 내부(inner) 클래스는 static으로 선언하라
- 내부클래스는 외부 클래스 인스턴스를 크게 만들며 또한 외부클래스 객체가 필요이상으로 오래 살아있게 되어 메모리를 더 차지할 수 있음
- 외부클래스의 상태 필드에 접근하지 않는(즉 외부 객체의 상태에 의존하지 않는) 내부클래스는 static으로 선언

* Attacking memory problems on Android



[출저] http://ecogeo.tistory.com/287

Posted by hoonihoon
2013. 11. 25. 10:07

프로그램에서 단 하나의 인스턴스(Instance)만 존재하게 하고 싶을 때 사용하는 패턴입니다.
어디서든지 그 인스턴스에 접근할 수 있기 때문에 전역 변수 등을 관리할 때 상당히 편리합니다.


싱글톤 패턴의 간단한 예는 다음과 같습니다. (다만, 아래의 코드는 멀티 쓰레드 환경에서 문제가 발생합니다.)

01.public class MyClass
02.{
03.private static MyClass m_MyClass = null;
04. 
05.private MyClass() {}
06. 
07.public static MyClass getInstance()
08.{
09.if (m_MyClass == null)
10.{
11.m_MyClass = new MyClass();
12.}
13. 
14.return m_MyClass;
15.}
16.}
위와 같은 경우는 멀티 쓰레드(Multi-Thread) 환경에서는 getInstace() 메소드가 끝나기 전에
각각의 쓰레드에서 접근이 가능하기 때문에 운이 없으면 인스턴스가 여러 개 생성될 수도 있습니다.


이 경우 해결 방법은 getInstance() 메소드를 synchronized로 동기화시키는 방법이 있습니다.
01.public static synchronized MyClass getInstance()
02.{
03.if (m_MyClass == null)
04.{
05.m_MyClass = new MyClass();
06.}
07. 
08.return m_MyClass;
09.}
하지만, 함수 전체에 synchronized는 동기화 과정에서 속도 문제가 발생할 수 있습니다.


좀 더 효과적으로 하려면 함수 전체에 synchronized를 거는 것이 아니라 함수 내부의 특정 구간에만 거는 방법입니다.
01.public static MyClass getInstance()
02.{
03.if (m_MyClass == null)
04.{
05.synchronized(MyClass.class)
06.{
07.if (m_MyClass == null)
08.{
09.m_MyClass = new MyClass();
10.}
11.}
12.}
13. 
14.return m_MyClass;
15.}


그리고 코드를 좀 더 깔끔하고 쉽게 가져가기 위해서는
 아예 처음부터 인스턴스를 생성해버리는 방법입니다.
01.public class MyClass
02.{
03.private static MyClass m_MyClass = new MyClass();
04. 
05.private MyClass() {}
06. 
07.public static MyClass getInstance()
08.{
09.return m_MyClass;
10.}
11.}
이 경우는 프로그램이 처음 실행되면서 바로 인스턴스가 생겨버리기 때문에 불필요한 부분에서 인스턴스가
메모리를 차지해버린다는 단점이 있지만, 멀티 쓰레드 동기화 문제에서 자유로울 수 있고 코드가 간결해진다는
장점이 있습니다. 

그리고 어차피 한 번은 메모리에 올라갈 인스턴스이기 때문에 미리 메모리에 실어 놓는 것도 나쁘지는 않겠죠. ^^; 

출처 - http://snowbora.com/442

=======================================================================

Singleton Pattern - 디자인 패턴

패턴 정의 #6 - Singleton Pattern

1. synchronized 키워드를 사용한다.

  1. public class Singleton {  
  2.     private static Singleton uniqueInstance;  
  3.    
  4.     // other useful instance variables here  
  5.    
  6.     private Singleton() {}  
  7.    
  8.     public static synchronized Singleton getInstance() {  
  9.         if (uniqueInstance == null) {  
  10.             uniqueInstance = new Singleton();  
  11.         }  
  12.         return uniqueInstance;  
  13.     }  
  14.    
  15.     // other useful methods here  
  16. }  

2. 인스턴스를 필요할 때 생성하지 말고, 처음부터 만들어 버린다.

  1. public class Singleton {  
  2.     private static Singleton uniqueInstance = new Singleton();  
  3.    
  4.     private Singleton() {}  
  5.    
  6.     public static Singleton getInstance() {  
  7.         return uniqueInstance;  
  8.     }  
  9. }  

3. DCL(Double Checking Locking)을 써서 getInstance()에서 동기화 되는 부분에 대한 체크를 줄입니다.

  1. public class Singleton {  
  2.     private volatile static Singleton uniqueInstance;  
  3.    
  4.     private Singleton() {}  
  5.    
  6.     public static Singleton getInstance() {  
  7.         if (uniqueInstance == null) {  
  8.             synchronized (Singleton.class) {  
  9.                 if (uniqueInstance == null) {  
  10.                     uniqueInstance = new Singleton();  
  11.                 }  
  12.             }  
  13.         }  
  14.         return uniqueInstance;  
  15.     }  
  16. }  

I. Singleton 이란?

아마 GOF의 32가지 패턴 중 가장 쉬우면서도 가장 자주 쓰이게 되며, 가장 문제가 될 소지를 가지는 패턴을 말하면 Singleton을 말할 수 있지 않을까 합니다.

먼저 Singleton 패턴의 용도는 하나의 프로그램 내에서 하나의 인스턴스만을 생성해야만 하는 상황. 예를 들어 환경설정을 관리하는 클래스나
Connection Pool, Thread Pool과 같이 풀(Pool) 형태로 관리되는 클래스의 경우 프로그램 내에서 단 하나의 인스턴스로 관리되는 것이 일반적이며, 이 때 Singleton 패턴을 적용하는 것이 일반적인 경우라고 볼수 있겠습니다. 그럼 세부적인 구현 형태를 살펴 보도록 하겠습니다.

II. Singleton 구현

Singleton 패턴의 가장 일반적인 형태는 다음과 같습니다.

singleton.bmp 

<일반적인 Singleton class diagram>

중요하게 보아야 할 곳은 생성자가 private으로 선언되어 있다는 것이죠. 이유는 간단하게도 클래스 외부에서 이 클래스를 직접 new 키워드로 생성하지 못하게 막겠다는 의미가 되겠습니다. Singleton 패턴은 설명보다는 코드로 보는 것이 훨씬 간결하므로 다음 코드를 보도록 하겠습니다.

  1. Singleton 패턴 

  2. class Singleton { 

  3. private static Singleton instance;

  4. private Singleton(){ 

  5. public static Singleton getInstance(){ 

  6. if (instance == null) 

  7. instance = new Singleton();  

  8. return instance; 

  9. }

 

   위 코드에서 파란색으로 표기된 선언부에 집중해야 합니다. Singleton 클래스의 인스턴스를 외부에서 사용하기 위해서는 항상 getInstance 메소드를 호출해야만 하도록 강제된 것을 확인하실 수 있습니다.  위와 같은 경우 getInstance가 호출될 때 객체가 생성되는데 최초 로딩 시에 객체를 생성해 두는 것이 효율적인 경우에는 다음과 같이 멤버 변수를 선언하는 곳에서 직접 생성하는 방법을 이용할 수 있습니다.

 

  1. ClassLoader에 의해 미리 인스턴스를 생성하는 방법 
  2. class Singleton { 
  3. private static Singleton instance = new Singleton(); 
  4. private Singleton(){} 
  5. public static Singleton getInstance(){ 
  6. if (instance == null) 
  7. instance = new Singleton(); 
  8. return instance; 

위의 코드에서 붉은색으로 표기된 멤버 변수 선언부를 보면 new 키워드에 의해 static으로 선언된 멤버 변수의 인스턴스를 미리 생성하는 것을 볼 수 있습니다.

다중 쓰레드 프로그래밍에 필요한 것이 아니라면 위의 코드만으로도 완성된 코드라고 볼수 있겠습니다. 미리 인스턴스를 생성할 필요가 없는 경우에는 이전의 방법을 이용하는데 그런한 방식을 lazy(게으른) 방식이라고 이야기 합니다.

다중 쓰레드 프로그래밍에서는 동기화를 이용한 적절한 수정이 필요한데 많은 커뮤니티에서 그에 따른 예제 코드가 잘 나와있습니다.

(Singleton의 다양한 형태 : http://www.anfamily.net/mcsong/70)

위의 블로그에 방문하시면 싱글톤 패턴의 다양한 종류와 그에 따른 장단점이 잘 분류되어 있습니다.

다만 좀더 자세히 내용을 들여다 보면 가장 간단한 방법이 오히려 득이 되는 경우를 찾을 수 있을 것 입니다. 다음 코드를 봐주세요.

  1. Instance의 생성은 단 한번만

  2. class Singleton{

  3. private static Singleton instance = new Singleton();

  4. private Singleton(){}

  5. public static Singleton getInstance(){

  6. return instance;

  7. }

  8. }

위의 코드의 녹색으로 표시된 부분을 봐주세요. Simple is best 라는 말 처럼 위의 코드는 Thread-safe하게 잘 만들어진 코드라고 할 수 있겠습니다.

이 클래스는 전체 라이프 타임 중에서 단 한번만 객체를 생성하고 다시는 생성하지 않습니다. 여러개의 쓰레드가 접근을 한다고 하더라도 인스턴스가 두개 이상 생성되거나 Null값을 리턴 받을 염려도 생성자가 두번 호출 되는 경우도 없죠. 

 

III. 결론

하나의 패턴은 다양한 형태로 표현되는 경우가 많습니다. 어쩔 때는 경계가 모호한 형태로 패턴이 구현되는 경우도 있지요.

이번 싱글톤 패턴은 목적과 형태가 분명한 패턴으로 항상 머리에 넣어두고 사용해야할 중요한 패턴이라고 말할 수 있습니다.

 

참고문헌


출처 -  http://sakula99.egloos.com/2971297 
 

===================================================================================


싱글톤(Singleton) Pattern 이란?



객체지향형언어(OOP)에 대해 조금이라도 파고든 사람이라면 싱글톤 패턴이라는 

말을 들어봤을 것이다. 못들어봤다면 이제부터 들었다고 해라. 싱글톤이란 생성하고자 

하는 인스턴스의 수를 오직 하나로 제한하는 디자인 패턴이다.



그렇다면 왜 싱글톤 패턴을 사용해야하는 것일까? 라는 질문에 대게 답하는 것이

여러개의 인스턴스를 생성하게 되면 가끔 프로그래머도 당혹스럽게 되는 서로의

인스턴스에 간섭을 하는 경우가 있다. 정말 재수 없는 일이 아닐 수가 없다.





public class Singleton

{

private static Singleton singleton = new Singleton();

protected Singleton()

{

System.out.println("Maked Singleton");

}

public static Singleton getInstance()

{

return singleton;

}

}



싱글톤의 기본형이다. singleton 멤버변수는 static 이어야한다는 것과 Singleton 클래스의

생성자는 private / protected 이어야한다는 것을 꼭 유념해야한다. private 일 경우는 결코

new 를 이용하여 인스턴스의 중복 생성을 방지하는 셈이기도 하나 상속이 되지 않는다는

단점이 있어 protected로 대게 선언한다.



뭐~ 싱글톤 패턴이 만들어졌나 아닌가 확인할 것이라면 Test 클래스를 만들어보자.



public class Test

{

public static void main(String [] args)

{

System.out.println("Singleton pattern");

Singleton sg1=Singleton.getInstance();

Singleton sg2=Singleton.getInstance();

if( sg1 == sg2 )

{

System.out.println("Equal");

}

else

{

System.out.println("Not Equal");

}

}

}



여기서 보면 Singleton 의 인스턴스를 생성하기 위해 getInstance() 메소드를 이용한다.

왜 그럴까? Singleton 클래스의 private static Singleton singleton = new Singleton(); 

부분을 유심히 바라보기 바란다. 이 singleton은 static으로 선언된다. 즉 하나의 인스턴스

singleton 만 생성하는 셈이다. 아마 결과도 Equal로 출력될 것이다.


출처 - http://coolx.net/cboard/read.jsp?db=develop&mode=read&num=261

출저 : linuxism.tistory.com/14



싱글톤 패턴의 여러형태  개발 / Study 

2010/02/09 11:34

복사http://blog.naver.com/k1qpo/99745837

전용뷰어 보기

아래 내용은 OKJSP에서 발췌를 하였습니다..
소스코드내에는 몇가지 싱글톤 구현방법이 나옵니다. 그 각각이 의미하는바는 다음과 같습니다.

1) EagerSingleton

  1. static class EagerSingleton extends Singleton {  
  2.   static final EagerSingleton theInstance = new EagerSingleton();  
  3.   static EagerSingleton getInstance() {  
  4.   return theInstance;  
  5.   }  
  6. }  

이 경우는 미리 싱글톤 인스턴스를 생성하는 방법으로 static final 필드에 인스턴스를 생성하여 할당하는 방법입니다. 이 필드는 클래스로더에 의해서 EagerSingleton이 메모리로 올라오는 순간에 안전하게(thread-safe하게)초기화 됩니다. 이 방법이 아마 성능이 가장 우수하게 나타날 것입니다. 
장점은 동기화부담이 적다는 것입니다. 단 한번 클래스로더가 thread-safe하게 클래스를 로드하는 시점에 클래스가 로딩되어 안전합니다. 단점은 인스턴스가 미리부터 생성된다는 것입니다. 

2) SynchedSingleton
  1. static class SynchedSingleton extends Singleton {  
  2.   static SynchedSingleton theInstance;  
  3.   static synchronized SynchedSingleton getInstance() {  
  4.    if (theInstance == null)  
  5.      theInstance = new SynchedSingleton();  
  6.    return theInstance;  
  7.   }  
  8. }  

이 방법은 싱글톤 필드에 접근할때마다 동기화를 수행하는 방법입니다. 매번 동기화를 수행하므로 자바 메모리 모델에 따른 각종 문제를 방지할 수 있어 thread-safe합니다. 단점은 매번동기화를 수행하므로 수행 비용이 높다는 것입니다.

3) ThreadLocalSingleton
  1. static class ThreadLocalSingleton extends Singleton {  
  2.   static final ThreadLocal perThreadInstance = new ThreadLocal();  
  3.   static final Object lock = new Object();  
  4.   static ThreadLocalSingleton theInstance;  
  5.   
  6.   static ThreadLocalSingleton getInstance() {  
  7.   ThreadLocalSingleton instance = (ThreadLocalSingleton)(perThreadInstance.get());  
  8.   if (instance == null) {  
  9.      synchronized(lock) {  
  10.        instance = theInstance;  
  11.        if (instance == null)  
  12.          instance = theInstance = new ThreadLocalSingleton();  
  13.      }  
  14.      // copy global to per-thread  
  15.      perThreadInstance.set(instance);  
  16.    }  
  17.      return instance;  
  18.   }  
  19. }  

이 방법은 Thread Local Storage를 사용한 해법입니다. 자바 쓰레드 메모리 모델에서 문제가 발생하는 것은 멀티 CPU상황에서 각각의 CPU가 자신의 캐시내에 클래스의 필드를 복사해넣는다는 것입니다. 이러한 캐시는 메인메모리와 동기화가 되어 있지 않습니다. 즉, 각각의 CPU가 하나의 클래스를 접근하게되면 각각의 CPU는 그 클래스의 내용을 다르게 볼 수 있다는 것입니다. 
싱글톤을 구현하면 instance라는 필드를 여러개의 CPU가 참조하는데 이 필드를 여러개의 CPU가 다르게 보게 됩니다. 예를들어 CPU A가 인스턴스를 생성하고 생성한 인스턴스를 instance에 할당한다고 합시다.
그러면 CPU A가 또다시 이 instance 필드를 참조할때는 생성된 인스턴스를 제대로 보게 됩니다. 그러나 CPU B가 이 instance필드를 참조할때는 instance필드가 null로 나타날 수 있습니다. 그 이유는 CPU A가 수행한 작업은 synchronized블록을 통과할때까지 메인메모리에 반영이 안되고 자신의 캐시내에만 담겨 있을 수 있으며, CPU B역시 synchornized블록을 통과하지 않으면 메인메모리가 아닌 자신의 캐시만
들여다보기 때문입니다.

이를 해결하려면 각각 CPU가 동기화블록을 들어갔다가 나와야만 하는데, 이를 구현한 것이 위의 코드입니다. 각각의 Thread는 자신만의 메모리공간으로서 TLS(Thread Local Storage)를 가지고 있으며 이들은 매 쓰레드마다의 공간이므로 동기화될 필요가 없습니다. 따라서 이 저장소에 해당 쓰레드가 synchronized블록을 한번이라도 다녀왔는지(한번이라도 다녀오면 CPU가 메인메모리의 값을 가져오니까요)를
저장해둡니다.

4) SimulatedThreadLocalSingleton
  1. static class SimulatedThreadLocalSingleton extends Singleton {  
  2.   static SimulatedThreadLocalSingleton theInstance;  
  3.   static final Object lock = new Object();  
  4.   static final Object key = new Object();  
  5.   
  6.   static Singleton getInstance() {  
  7.   TSS t = (TSS)(Thread.currentThread());  
  8.   Singleton instance = (Singleton)(t.threadLocalHashtable.get(key));  
  9.   if (instance == null) {  
  10.      synchronized(lock) {  
  11.        instance = theInstance;  
  12.        if (instance == null)  
  13.          instance = theInstance = new SimulatedThreadLocalSingleton();  
  14.      }  
  15.      // copy global to per-thread  
  16.      t.threadLocalHashtable.put(key, instance);  
  17.   }  
  18.   return instance;  
  19.   }  
  20. }  

이 방법은 TLS를 ThreadLocal 클래스를 쓰지 않고 직접구현한 방식입니다.

5) VolatileSingleton

  1. static class VolatileSingleton extends Singleton {  
  2.   static final Object lock = new Object();  
  3.   static volatile VolatileSingleton theInstance;  
  4.   
  5.   static VolatileSingleton getInstance() {  
  6.      VolatileSingleton instance = theInstance;  
  7.      if (instance == null) {  
  8.        synchronized(lock) {  
  9.         instance = theInstance;  
  10.         if (instance == null)  
  11.           instance = theInstance = new VolatileSingleton();  
  12.       }  
  13.     }  
  14.   return instance;  
  15.   }  
  16. }  

주의!)이 방법은 절대로 사용해서는 안됩니다. 
이 방법은 volatile를 사용합니다. volatile 로 선언된 필드는 매번 원자적으로 쓰레드 safe하게 이루어집니다. 즉 각각의 변수에 대한 접근이 매번 메인메모리와 동기화될 뿐만아니라, thread safe하게 이루어집니다. synchronized와 volatile은 이처럼 변수의 접근마다 동기화를 하느냐 아니면 특정 블록을 통채로 동기화
하는냐의 문제에 있어서 접근방법이 틀립니다. 그러나 아쉽게도 volatile은 대부분의 자바 컴파일러에서 제대로 구현되어있지않으며 따라서 사용하는것을 권하지 않습니다. SUN의 컴파일러는 제대로 되지 않느냐 하고 생각하실 수 있지만 전혀 안그렇습니다. SUN의 JDK 1.3대에서도 제대로 구현이 되어있지 않습니다.
volatile은 동기화의 문제를 비롯한 다양한 암시적 작동이 보장되어야하는데 이를 제대로 책임지고 않기때문입니다.

6) DirectThreadFieldSingleton
  1. static class DirectThreadFieldSingleton extends Singleton {  
  2. static DirectThreadFieldSingleton theInstance;  
  3. static final Object lock = new Object();  
  4.   
  5. static Singleton getInstance(TSS t) {  
  6.   Singleton instance = t.singleton;  
  7.   if (instance == null) {  
  8.     synchronized(lock) {  
  9.       instance = theInstance;  
  10.       if (instance == null)  
  11.         instance = theInstance = new DirectThreadFieldSingleton();  
  12.     }  
  13.     // copy global to per-thread  
  14.     t.singleton = instance;  
  15.   }  
  16.   return instance;  
  17. }  
  18. }  

인스턴스를 할당받고자하는 쪽에서 TSS라는 형태의 클래스를 쓰레드마다 할당한채로 갖고 있다가 이것을 싱글톤 클래스에 넘깁니다. 그러면 TSS.singleton변수의 값을가지고 동기화 수행여부를 결정하는 방식입니다. ThreadLocal의 변형이며, 모든 인스턴스를 획득하고자하는 쓰레드가 TSS를 넘겨야한다는 점에서
좋은 방법은 아니겠죠.

7) ThreadFieldSingleton
  1. static class ThreadFieldSingleton extends Singleton {  
  2. static final Object lock = new Object();  
  3. static ThreadFieldSingleton theInstance;  
  4.   
  5. static Singleton getInstance() {  
  6.   TSS t = (TSS)(Thread.currentThread());  
  7.   Singleton instance = t.singleton;  
  8.   if (instance == null) {  
  9.     synchronized(lock) {  
  10.       instance = theInstance;  
  11.       if (instance == null)  
  12.         instance = theInstance = new ThreadFieldSingleton();  
  13.     }  
  14.     // copy global to per-thread  
  15.     t.singleton = instance;  
  16.   }  
  17.   return instance;  
  18. }  
  19. }  

이 방법역시 ThreadLocal의 구현에 대한 변형된 구현입니다. ThreadLocal 대신, 특정 싱글톤 클래스에 대한 인스턴스를 획득하려고 시도하는 쓰레드를 캐스팅해서 ThreadLocal을 구현한 것이죠. 개인적으로 이 방법은 별로 좋지 않다고봅니다. 특정 클래스의 인스턴스를 획득하려면 쓰레드가 어떠한 인스턴스를 구현하고 있는지는 쉽사리 가정하기가 곤란하죠.


Posted by hoonihoon
2013. 11. 22. 11:00

결론:

SELECT * FROM tb_b1 WHERE b_index > 10 ORDER BY b_index ASC LIMIT 1;  [이전글]

SELECT * FROM tb_b1 WHERE b_index < 23 ORDER BY b_index DESC LIMIT 1; [다음글]

참조:

http://www.phpschool.com/gnuboard4/bbs/board.php?bo_table=tipntech&wr_id=19972&sca=&sfl=wr_subject%7C%7Cwr_content&stx=%C0%C0%B4%E4%C7%FC+%B0%D4%BD%C3%C6%C7&sop=and

시판에서 글보기를 했을시 하단에 전체 글목록을 보여주는 방법도 있지만

현재 보는글과 관련된글(답글)을 보여주면서 동시에 이전글, 다음글로 바로 이동하게 하는 방법도 있습니다.

이전글, 다음글로 바로 이동할때는 글을 보는 페이지(view.php)에서 이전글, 다음글에 해당하는 데이터를 알아야 합니다.

우선 묻지마보드의 경우에는 글보기를 할때 키값이 글번호입니다.

그럼 현재 보는글에서 다음글, 이전글에 대한 글번호를 구할수가 있어야 합니다.


예전에 초기버전의 묻지마보드의 경우에는 디비에 저장되는 글번호의 형식이 auto_increment 형식이 아닌

일반 int 형식으로 글을 작성할때마다 직접적인 번호조작에 의해서 1부터 차례대로 디비에 저장이 되게 됩니다.

그래서 이전글, 다음글을 불러올때는 아주 간편하게 해결이 됩니다.


예전 게시판처럼 글번호가 1,2,3,4 이렇게 무조건 순차적으로 저장이 될경우에는

이전글은 현재보는 글번호 + 1

다음글은 현재보는 글번호 - 1

이런식으로 되게됩니다. 그렇다면 이렇게 간편한데.. 왜 그런 방식을 채택하지 않냐는 의문이 생깁니다.

바로 효율성때문입니다. 현재는 글번호 필드가 auto_increment로 저장이 되며 글삭제시 그 삭제된부분의 

번호는 뻥뚤리게 됩니다. 그러면 글목록을 불러올때 글번호가 차례대로 출력되는건 가상번호때문입니다.

이 가상번호란 디비에 저장되어있는것이 아니라 리스팅할때 list.php 파일속에서 자체적으로 번호를 순차적으로 만들어 줍니다.


현재 묻지마보드의 경우 이러한 가상번호로 리스팅할때 순차적으로 번호가 주어지게 되며, 실제로 디비에 저장되는

글번호(no)는 아래 그림처럼 글이 삭제된경우에는 순차적인게 아니라 삭제된 부분의 번호는 비어있게 됩니다.


<그림 1>



답글이 달려도 그러하고 글이 삭제된 경우에도 그부분에 해당하는 글번호는 비게됩니다.

이러한 경우에는 이전글, 다음글을 가져올때 위의 방법처럼 1을 더하고 1을 뺀다고해서 해결될 문제가 아닙니다.

운이 좋다면 1을 더했을때 이전글을 추출할수 있을것이고 운이 나쁘다면 못할것입니다. 근데 우리는 운을 믿어서는 안되죠..^^


이제 본격적으로 이전글, 다음글을 구하는 Logic에 대해서 알아보도록 하겠습니다.

기본 원리는 시간을 이용한것입니다.


<그림 2>




이전글은 현재 보고있는 글보다 분명 먼저 작성된 글이다. (즉, date 값이 작음)

다음글은 현재 보고있는 글보다 분명 이후에 작성된 글이다. (즉, date 값이 큼)


여기서 말하는 date값이란 디비에 저장되는 글쓴시간의 타임스탬프 값입니다.


<그림 2>에서 현재 보고있는글은 503번 글이라고 한다면 우리에게 필요한것은 504번글과 502번글의 글 고유번호입니다.

이것을 구하기 위해서 503번글이 작성된 시간을 바탕으로 쿼리를 날리게 됩니다.


<그림 3>



이 쿼리에서 que1은 다음글을 구해오는 쿼리이며 que2는 이전글을 구하는 쿼리입니다.

que1을 보면 디비에서 date가 현재 우리가 보는 글이 쓰여진 날짜($row[date])보다 작은걸 가져옵니다.

즉, <그림2>에서 503번글 밑으로의 글들을 말하는거죠.

근데 이때 문제는 502번, 501번, 500번 즉, 503번 밑으로 모든 글들은 날짜가 $row[date]보다 작습니다.

그래서 뒤에 따라오는게 바로 "order by no desc limit 1" 입니다.

즉, 날짜가 작은것 중에서 역순으로 딱 한개만 쿼리하는거죠. 결과적으로 말하면 이 쿼리가 바로 <그림2>에서 502번을 가져오는거죠.


이전글을 구하는 쿼리 역시 같습니다. 위에서 설명한대로 하지만 단 한가지 다른점은 이번에는 역순(desc)으로 가져오는게 

아니라 정순(asc)로 가져와야합니다. 잘 생각해보시면 이해가 될겁니다. <그림2>에서 503번 위쪽에서 딱 하나를 가져올때는

가장 밑에 글을 가져와야만 그게 504번글이 되기때문이죠.


이해가 되셨나요??

이부분이 이전글, 다음글을 구하는 알고리즘의 가장 핵심부분입니다.

이것만 view.php 파일속에 제대로 삽입했다면 그다음에는 여러분이 원하시는데로 만들수 있습니다.

저같은 경우에는 다음 그림처럼 보이게 만들었습니다.


<그림 4>

위 그림에서 아래쪽 화살표를 클릭하면 다음글을.. 위쪽 화살표를 클릭하면 이전글을 보게 됩니다.

근데 저는 여기에 약간의 기능을 더 추가했습니다.


이전글이나 다음글이 없다면, 즉, 현재 보는글이 가장 첨의 글이거나 가장 나중의 글이라면

이전글, 다음글 버튼을 회색으로 처리했습니다. 그리고 그 버튼을 눌르면 "존재하지 않는 글을 선택하셨습니다." 라는

경고창을 띄우고 되돌려 보내게 했습니다.


이부분의 구현방법은 <그림 3>과 아래 그림을 잘 비교해보시면 이해가 갈겁니다.

<그림 3>은 게시판 자체 view.php 파일이며, 아래 그림은 스킨폴더속의 view_foot.php파일의 한 부분입니다.


<그림 5>



이제 구현하시겠습니까??

사실 간단해 보이지만 이 부분을 구현해내기위해서 혼자서 데이터베이스의 필드를 조작해보기도 하고

새로운 필드(prev_no, next_no)를 추가해보기도 했습니다. 근데 너무 일이 복잡해질듯해서 생각해본 결과

이렇게 간단하게 해결이 되더군요. 한번 해보시기 바랍니다.

Posted by hoonihoon