'Android'에 해당되는 글 155건

  1. 2010.07.06 Using WebView 3
  2. 2010.05.31 Gestures 2
  3. 2010.05.24 타이머 사용 시 주의사항 1
  4. 2010.05.19 Android Graphics #1 5
  5. 2010.05.17 WeakReference 와 SoftReference 좀 쉽게 정리
  6. 2010.05.17 Memory leak & Weak Reference 1
  7. 2010.05.04 app2sd 사용법 - 생각보다 간단함. 3
  8. 2010.04.28 Custom View 만들기 3
  9. 2010.04.26 터치모드 정리 1
  10. 2010.04.26 articles of Layout Tricks 1
Android/App개발2010. 7. 6. 22:17

원문 : http://developer.android.com/resources/articles/using-webviews.html

 

링크 : http://silence2.tistory.com/entry/Using-WebViews


번역 : 이상훈 (calm1979@gmail.com)

2010년 6월 30일

 

WebViewDemo는 어플리케이션에 웹 컨텐츠를 붙일 수 있는 방법을 보여주는 간단한 어플리케이션이다. 이것은 apps-for-android 프로젝트에서 찾을 수 있다. 이 어플리케이션은 액티비티 내에 WebView를 붙일 수 있는 방법과 어플리케이션과 웹 컨텐츠 사이에 서로 통신할 수 있는 방법을 보여준다.

 

WebView는 브라우저와 동일한 렌더링 엔진과 자바 스크립트 엔진을 사용하지만, 당신의 어플리케이션의 제어하에 동작한다. WebView는 전체 화면으로 동작할 수도 있고, 다른 View들과 섞여서 동작할 수도 있다. WebView의 컨텐츠는 어디서든지 올 수 있다. WebView는 웹에서 컨텐츠를 다운로드 하거나, 당신의 assets 디렉토리 내에 저장된 로컬 파일에서 컨텐츠를 얻을 수도 있다. 심지어 어플리케이션 코드 상에서 동적으로 생성된 컨텐츠를 사용할 수도 있다. 이 예제에서는 demo.html 이라는 로컬 파일을 표시한다.

 

이 어플리케이션은 많은 것을 하지는 않는다: 당신이 안드로이드를 클릭하면 팔을 들어줄 뿐이다.


물론 이것은 간단한 자바 스크립트로 처리할 수 있다. 하지만, WebViewDemo는 WebView의 매우 강력한 두 가지 기능을 알려주기 위해 약간 복잡한 방법을 사용했다.

 

첫 째, WebView 내에 돌고 있는 자바 스크립트에서 액티비티의 코드를 호출할 수 있다. 당신은 이런 것을 자바 스크립트가 새로운 액티비티를 시작하는 액션을 발생시키거나, 데이터베이스나 ContentProvider에서 데이터를 얻어내는 경우 등에 사용할 수 있다. 이를 위한 API는 매우 간단하다: WebView의 addJavascriptInterface()만 호출하면 된다. 자바 스크립트에 노출시킬 메소드를 가진 객체와 자바 스크립트에서 호출할 때 사용할 이름을 넘겨주면 된다. 정확한 문법은 WebViewDemo.java에서 확인할 수 있다. 여기에서는 자바 스크립트에서 "window.demo"로 호출해서 사용될 DemoJavascriptInterface 객체를 만들었다.

 

둘 째, 액티비티에서 자바 스크립트 메소드를 호출할 수 있다. 이것은 모두 loadUrl 메소드를 통해 적절한 자바 스크립트를 호출 해야한다.

1 mWebView.loadUrl("javascript:wave()");


WebViewDemo 는 두 가지 기술을 사용하고 있다: 당신이 안드로이드를 클릭하면, 액티비티로 호출을 하고 다시 자바 스크립트를 호출한다. WebViews는 매우 강력하고, 어플리케이션을 만드는데 충분히 도움이될 만한 툴이다. - 특히 많은 양의 HTML 컨텐츠를 가지고 있는 경우에 도움이 될 것 이다. 마침, 우리는 우리가 만들고 있는 어플리케이션의 일부에서 정확히 이 방식을 사용했었다.


Posted by 삼스
Android/정리2010. 5. 31. 14:39

Gestures

터치스크린은 모바일장치에서 애플리케이션과 연동하기 위한 대단한 방법이 있다. 터치스크린을 통해 사용자는 tab, drag, fling또는 slide 통해 애플리케이션을 빠르게 액션을 수행할 있다. 개발자를 위해서 안드로이는 framework swite같은 간단한 액션을 인식할 있는 방법을 제공한다. 하지만 복잡한 제스쳐는 다루기 어렵다 간혹 개발자는 이런 복잡한 처리를 위해 추가적으로 많은 코딩을 해야 수도 있다.

Android 1.6(Donut)에서부터 새로운 제스쳐API 제공한다. Android.gesture 패키지가 제공되며, 인식가능한 제스쳐들을 store, load, draw할수 있다. 문서는 패키지의 사용방법을 설명한다. 시작하기 전에 예제를 먼저 다운받아라(download the source code of the examples).

Creating a gestures library

Android 1.6이후 버전에는 Gestures Builder라는 어플이 설치된다. 어플로 미리정의된 당신만의 제스쳐를 만들 있다. 예제가 함께 제공되는데 제스쳐셋을 만들 있다. 아래 그림은 가지 제스쳐를 추가한 이후의 화면을 보여준다.

위에서 보는바와 같이 제스쳐는 연관된 이름을 갖는다. 이름은 앱에서 각각 제스쳐를 구분하는데 사용되므로 아주 중요하다. 이름이 유일할 필요는 없다. 동일명하에 여러가지 제스쳐가 사용될 있다. 제스쳐빌더를 이용하여 제스쳐를 추가하거나 수정할 있으며 SD Card gestures폴더에 저장된다. 파일에는 제스쳐에 대한 정보가 저장되어 있으며 앱에서 /res/raw폴더에 위치시켜서 사용할 있다.

Loading the gestures library

이제 미리정의된 제스쳐를 갖게 되었다. 이제 에서 반드시 로드하여 사용해야 한다. 몇가지 방법이 있으며 가장 간단한 방법은 GestureLibraries 클래스를 사용하는 방법이다.

mLibrary = GestureLibraries.fromRawResource(this, R.raw.spells);
if (!mLibrary.load()) {
    finish
();
}

예에서, /res/raw/spells파일로부터 제스쳐가 로드되었다. SD card같은 다른 소스로부터 로드할 수도 있으며 앱에서 파일을 저장할 수도 있으며 변경할수 없더록 읽기전용으로 로드할수도 있다. 아래는 하나의 라이브러리의 구조를 다이어그램으로 보여준다.

Recognizing gestures

To start recognizing gestures in your application, all you have to do is add a GestureOverlayView to your XML layout:

앱에서 제스쳐를 인식하기 위해서 해야 하는 일은 XML layout GestureOverlayView 추가하는 뿐이다.

<android.gesture.GestureOverlayView
   
android:id="@+id/gestures"
   
android:layout_width="fill_parent"
   
android:layout_height="0dip"
   
android:layout_weight="1.0" />

GestureOverlayView 일반적인 android.widget package 일부가 아니란걸 알아야 한다. 그러므로 full name 사용해야 한다. Gesture overlay 사용자가 제스쳐를 그리는 영역에서 동작한다. 몇가지 visual 속성들을 변경할 있다. 색상이나 stroke 두께, 그리고 액션이 진행동안의 사용되는 다양한 listener등을 변경할 있다. 가장 일반적으로 많이 사용되는 listener GestureOverlayView.OnGesturePerformedListener이다. gesture 그려질때마다 호출된다.

GestureOverlayView gestures = (GestureOverlayView) findViewById(R.id.gestures);
gestures
.addOnGesturePerformedListener(this);

Listener 호출될 GestureLibrary 제스쳐를 인식하기 위한 질의를 보낼 있다. 리턴값에서 예측된값들의 리스트와 score 얻게 될것이다. 리스트는 core 역순으로 정렬되어 있다. 가장 고득점에 해당하는 gesture 사용자가 그리길 원하는 것일것이다. 아래 코드는 첫번째 예측값의 이름을 검색하는 방법을 보여준다.

public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {
   
ArrayList<prediction> predictions = mLibrary.recognize(gesture);

   
// We want at least one prediction
   
if (predictions.size() > 0) {
       
Prediction prediction = predictions.get(0);
       
// We want at least some confidence in the result
       
if (prediction.score > 1.0) {
           
// Show the spell
           
Toast.makeText(this, prediction.name, Toast.LENGTH_SHORT).show();
       
}
   
}
}

예제에서는 예측값이 1.0이상인경우만 고려하였다. 1.0이하는 낮은 일치율을 의미하며 코드가 앱에서 미리정의된 제스쳐를 인식하는 코드의 전부이다.

 

Gestures overlay

예제코드에서 GestureOverlayView LinearLayout 안에 들어간 일반적인 뷰로 사용되었다. 하지만 이름에서 느껴지는 처럼, 다른뷰들의 최상위에 overlay 사용될 수도 있다. 이는 게임이나 앱의 어느곳에서든 사용될 있는 유용한 방법이다. 두번째 데모인 GetstureListDemo에서는 Contacts list 최상위에 overlay 생성할것이다. 그리고 Getsture builder 새로운 제스쳐를 정의할 것이다.

And here is what the XML layout looks like:

<android.gesture.GestureOverlayView
   
xmlns:android="http://schemas.android.com/apk/res/android"
   
android:id="@+id/gestures"
   
android:layout_width="fill_parent"
   
android:layout_height="fill_parent"
   
   
android:gestureStrokeType="multiple"
   
android:eventsInterceptionEnabled="true"
   
android:orientation="vertical">

   
<ListView
       
android:id="@android:id/list"  
       
android:layout_width="fill_parent"
       
android:layout_height="fill_parent"  />

</android.gesture.GestureOverlayView>

앱에서 일반적인 ListView 최상위에 gesture overlay 있다. 그리고 overlay에는 전에 본적없는 속성들이 몇가지 있다.

·         gestureStrokeType: single stroke 사용할지 또는 multiple stroke 사용할 지정한다.

·         eventsInterceptionEnabled: true 설정하면 gesture 처리할 있는 child view 있는 경우 child 이벤트가 전달되도록 한다. Scroll 가능한 view 있는경우 유용하며 scroll이벤트와 충돌이 발생하지 않는다.

·         orientation: scroll orientation 지정한다. 여기서는 vertical 하였으며 이것은 가로제스쳐인 action_delete 바로 제스쳐로 인식될수 있음을 의미한다. 세로 제스쳐의 경우 반드시 가로제스쳐를 하나이상 가져야만 인식이 가능하게 된다. 다른 의미로 세로로 그려진 라인은 스크롤과 오인될 있으므로 이런 옵션이 필요하게 된다.

·          

Gesture library load하고 setup하는것과 overlay 설정하는 것은 위에서 설명한 코드와 동일하다. 오직 한가지 다른 점은 이제 예측의 이름값을 이용하여 무엇을 했는지 알아낸다는 것이다.

public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {
   
ArrayList<Prediction> predictions = mLibrary.recognize(gesture);
   
if (predictions.size() > 0 && predictions.get(0).score > 1.0) {
       
String action = predictions.get(0).name;
       
if ("action_add".equals(action)) {
           
Toast.makeText(this, "Adding a contact", Toast.LENGTH_SHORT).show();
       
} else if ("action_delete".equals(action)) {
           
Toast.makeText(this, "Removing a contact", Toast.LENGTH_SHORT).show();
       
} else if ("action_refresh".equals(action)) {
           
Toast.makeText(this, "Reloading contacts", Toast.LENGTH_SHORT).show();
       
}
   
}
}

사용자는 이제 리스트의 위에 스크롤과 충돌없이 제스쳐의 입력이 가능해진다.

Overlay 세로스크로크만 입력했을 경우 반투명한 컬러로 표시하여 유효하지 않은 제스쳐임을 표시한다.

It's your turn

당신의 앱에 제스쳐를 추가하는 것은 쉬우면서도 가치있는 추가기능이 될 수 있다. 현재는 아주 쉬운 입력만 가능하나 활용하기에 따라 여러가지 모양을 인식하게 할수도 있다.. 알아서 잘 활용하길..

 

Posted by 삼스
Android/App개발2010. 5. 24. 21:20
java.util.Timer와 java.util.TimerTask를 이용한 타이머스레드를 사용할 때 

import java.util.Timer;
import java.util.TimerTask;

// 1. Timer생성
private Timer mTimer = new Timer();

// 2. TimerTask연결 하여 기동
//     INTERVAL시간마다 run()함수가 수행된다.
mTimer.schedule(
new TimerTask(){
public void run() {
mHandler.post(new Runnable(){
public void run(){
Log.d(TAG, "running Timer...");
}
});
}        
   }, INTERVAL,INTERVAL);

// 3. Timer의 제거
mTimer.cancel();

주의>>
** 생명주기의 관리에 주의해야 한다.
  BACK시에는 cancel후 재진입시 schedule을 호출시 정상동작하나..
  HOME시에는 cancel후 재진입시 schedule을 호출하면 IllegalStateException이 발생하면서 타이머가 동작하지 않는다.
  따라서 Timer는 이런식으로 사용하는것을 지양해야 할듯!!!!
  원인은 찾지 못함.

Posted by 삼스
Android/정리2010. 5. 19. 18:00

Graphics

Android 그래픽은 2D graphic library OpenGL 이용한 3D그래픽이 준비되어 있다. 2D graphic drawable package 공통으로 사용되는 API들이 정의되어 있으며, 3D graphic Khronos OpenGL ES package Android OpenGL utility들로 구성되어 있다.

Android graphics are powered by a custom 2D graphics library and OpenGL ES 1.0 for high performance 3D graphics. The most common 2D graphics APIs can be found in the drawable package. OpenGL APIs are available from the Khronos OpenGL ES package, plus some Android OpenGL utilities.

Canvas 사용하는 방법은 View 구조에서 어떻게 그려야 하는지에 대해 좋은 정보를 제공할것이다.

 

Consider your Options

2D 그래픽 그리기

1.     Layout View 객체에 graphic이나 animation 그린다. 그리기 작업은 시스템의 일반적인 View구조의 그리기 순서에 따라 그려지게 된다. 간단하게 View내부에 graphic 정의한다.

2.     Canvas 직접 그린다. 해당 클래스의 draw() 호출하거나 Canvas draw…() 호출하여 그림

 

1 View 그리기는  가장 편하고 좋은 선택이다. Simple Graphics Inside a View. <- 참조

2 Canvas 그리기는 정기적으로 다시 그리기가 필요한 경우에 좋은 선택이다. Video game들은 Canvas 그리기 방법을 사용한다.

·         UI Activity 동일한 스레드상에서 layout custom View component 생성되어 있는 경우 invalidate() 호출하면 onDraw()콜백이 동작한다.

·         또는 별도의 스레드의 경우 동일스레드에서 처리하는 만큼 Canvas 그리기를 수행하고 SurfaceView 다루는 것이 빠르다. ...Begin by reading Draw with a Canvas.

Simple Graphics Inside a View

단순한 그래픽(이미지, 모양, 색상, 미리만들어진 애니매이션등..) 그리고자 한다면 View 백그라운드를 그리고 layout안에 ImageView content영역에 그리기만 하면 된다. 그렇다면 문서를 건너뛰고 어떻게 그래픽과 애니매이션을 그리는지 배우길 바란다(2D Graphics 참조)

Draw with a Canvas

별도의 그리기를 구현하고자 한다면 Canvas 이용해서 그려야 한다.

onDraw()콜백메소드에 Canvas객체가 제공되며 이를 이용하여 그릴수 있다. SurfaceHolder.lockCanvas 통해 canvas 얻을수도 있으며 이는 SurfaceView객체로 다룰 가능하다.

Canvas 별도로 새로 생성하고자 할때는 반드시 실제로 그려지게될 Bitmap 정의해야 한다. Bitmap Canvas 항상 필요한 존재이다. 아래와 같이 새로운 Canvas 셋업할수 있다.

Bitmap b = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);

이렇게 생성된 canvas Canvas.drawBitmap(Bitmap, …)메소드중 하나를 이용하여 다른 canvas 복사할 있다. View.onDraw() SurfaceHolder.lockCanvas() 통해 얻어진 Canvas 통해서 그리는 것을 권장한다.

drawBitmap(…), drawRect(…), drawText(…) 많은 함수 지원. 다른 클래스들도 draw()메소드를 지원한다. Drawable클래스 같은 경우 자신의 draw()메소드를 지원하여 Canvas 인자로 사용되어 그려질 있다.

On a View

어플리케이션이 프로세싱이 많거나 frame-rate속도를 요하지 않는 경우(chess game같은 느린 애니매이션을 처리하는 게임등), View.onDraw()에서 Canvas 그리는 customView 만들어 사용할 있다. 이렇게 하면 안드로이드 프레임워크에서 제공하는 Canvas 이용하여 쉽게 구현가능하다.

1.     View class extend한다.

2.     onDraw() callback 정의한다. -> 메소드는 프레임워크에 의해 뷰가 그려져야 호출될것이다. draw관련 처리를 하는 지점이며 Canvas객체가 넘겨져 와서 객체를 다루면 된다.

안드로이드 프레임워크는 필요할때만 onDraw() 호출한다. 그려져야 하는 싯점이 되면 애플리케이션에서는 invalidate() 호출함으써 다시 그려지게 있다.

View component onDraw()에서는 Canvas객체를 사용하여 drawing 처리한다. Canvas draw…() canvas 넘겨져온 클래스의 draw()메소드가 호출되어 drawing 완성된다. 완성이 되면 프레임워크는 시스템에 의해 처리되는 비트맵을 그리기 위해 canvas 사용할것이다.

Note:  main activity thread 아니라 다른 thread에서 다시그리기를 수행하려면 postInvalidate() 호출해야 한다.

View class 확장에 대한 안내는 Building Custom Components  참조하라.

For a sample application, see the Snake game, in the SDK samples folder: <your-sdk-directory>/samples/Snake/.

On a SurfaceView

SurfaceView View구조내에서 surface 그리는 목적으로 고안된 특별한 subclass이다. 목적은 애플리케이션의 두번째 스레드에서 별도로 그려서 시스템의 View구조에서 그리기 타임까지 기다리지 않도록 하게 하기 위해 고안되었다. 두번째 thread에서 참조하는 SurfaceView 자신만의 Canvas 그리기를 수행한다.

1.     SurfaceView extend 새로운 클래스를 선언한다.

2.     SurfaceHolder.callback implement한다. 콜백 subclass 하부의 Surface 대한 정보와 함께 통지하는데 생성, 변경 또는 제거되었을 통지하게 된다. 이벤트를 통해서 언제 그리기를 시작해야 하는지 알수 있고, 새로운 surface속성에 맞게 조정하고, 언제 그리기를 종료하고 테스트를 죽일지등을 알수 있다. SurfaceView에서 두번째 Thread 정의하여 모든 drawing procedure 수행하는 것이 좋다.

3.     SurfaceHolder 생성한다. Surface 객체를 직접 처리하지 않고 SurfaceHolder 통해서 하는 것이 좋다. SurfaceView 초기화되면 getHolder() SurfaceHolder 얻는다.

4.     addCallback() 호출하여 SurfaceHolder.Callback으로부터 이벤트를 수신하도록 한다. SurfaceHolder.Callback SurfaceView클래스내에서 override하여 addCallback()한다.

5.     두번째 thread에서 Surface Canvas draw하려면 반드시 SurfaceHandler 전달해야 하며 lockCanvas() canvas 얻어야 한다. 이렇게 해야 SurfaceHolder 제공해준 Canvas 얻게 된다.

6.     Canvas drawing 한번 수행하면 canvas object 인자로 unlockCanvasAndPost() 호출해야 한다. lockCanvad() unlockCanvasAndPost() draw 반드시 쌍으로 호출되어야 한다.

 

Note: 한번 canvas 얻어져서 사용되면 canvas 이전상태를 유지한다. 따라서 적절하게 animate 하려면 surface전체를 re-paint해야 한다. 경우 drawColor() drawBitmap()등으로 background 갱신하면 된다. 그렇게 하지 않으면 이전 이미지가 표시될 것이다.

 

For a sample application, see the Lunar Landar game, in the SDK samples folder: <your-sdk-directory>/samples/LunarLander/. Or, browse the source in the Sample Code section.

 

Posted by 삼스
Android/App개발2010. 5. 17. 15:18
WeakReference
 용어그대로 약한 레퍼런스이다. 메모리관리를 위해 고안된것으로 레퍼런스를 WeakReference로 하면 gc에 해당 레퍼런스가 관여되지 않는다.

private WeakReference<Launcher> mLauncher; // 변수선언
void setLauncher(Launcher launcher) {
    mLauncher = new WeakReference<Launcher>(launcher); // 참조를 WeakReference로 저장함.
}
if (mLauncher != null) {
    final Launcher launcher = mLauncher.get(); // 사용시에는 get을 호출하여 null을 반드시 체크하여 사용
        if (launcher != null) {
            launcher.loadWallpaper();
        }
    }



SoftReference
 WeakReference 보다는 약간 강한 참조이다. Soft reference 만 남아있는 객체는 메모리의 요청에 의한 gc 의 대상이 된다. 메모리의 남은 공간이 넉넉하다면 gc되지 않을 것이며, 메모리의 남은 공간이 얼마 없거나 하다면 gc 의 대상이 될 것이다. 
확실한건 vm 이 OutOfMemoryError 를 발생시키기 전에는 Soft reference 만 남아있는 객체가 gc의 대상이 된다는 점 정도고, 그 이외에는 vm 의 구현에 따라 다를 수 있다.





Posted by 삼스
Android/App개발2010. 5. 17. 11:30

출처 : http://skyswim42.egloos.com/3677368

참조 http://www.ibm.com/developerworks/kr/library/j-jtp11225/#3.0

2005 년 11 월 22 일

자바 언어의 프로그램은 이론적으로는 "메모리 누수"에 대한 면역성이 있지만, 어떤 경우에는 프로그램의 논리적 상태의 일부가 더 이상 아닌데도 객체가 가비지 컬렉팅(garbage collected)이 되지 않은 상황이 있을 수 있다. 이번 달, Brian Goetz는 의도하지 않은 객체유지에 대해 설명한다.

가비지 컬렉터가 프로그램에서 더 이상 사용되지 않는 객체들을 처리하려면 객체의 논리적 수명(애플리케이션이 객체를 사용하는 시간)과 그 객체에 대한 레퍼런스의 실제 수명이 같아야 한다. 대부분의 경우 이 일은 자동으로 처리되기 때문에 우리가 객체의 수명 문제까지 신경 쓰지 않아도 된다. 하지만 가끔씩은 우리가 예상했던 것 보다 훨씬 긴 시간동안 메모리에 객체를 보유하고 있는 레퍼런스를 만들 때가 있다. 이 상황을 의도하지 않은 객체유지(unintentional object retention)라고 한다.

메모리 누수와 글로벌 맵

의도하지 않은 객체 유지의 가장 일반적인 원인은 Map을 사용하여 메타데이터와 임시 객체들을 연결하는데 있다. 예를 들어, 클라이언트로부터의 소켓 연결 같은 중간 수명(메소드 호출 보다는 길지만 애플리케이션 보다는 짧은 수명)을 가진 객체가 있다고 가정해 보자. 몇 개의 메타데이터를 그 소켓과 연결해야 한다 그 당시에는 Socket이 만들어진다는 사실을 모르고, Socket 클래스나 인스턴스를 제어할 수 없기 때문에 Socket 객체로 데이터를 추가할 수 없다. 이 경우, 전형적인 방식은 그와 같은 정보를 글로벌 Map에 저장하는 것이다. Listing 1의SocketManager 클래스를 보자.


Listing 1. 글로벌 Map을 사용하여 메타데이터와 객체 제휴하기 
public class SocketManager {    
    private Map<Socket,User> m = new HashMap<Socket,User>();
    public void setUser(Socket s, User u) {
        m.put(s, u);
    }
    public User getUser(Socket s) {
        return m.get(s);
    }
    public void removeUser(Socket s) {
        m.remove(s);
    }
}
SocketManager socketManager;socketManager.setUser(socket, user);

이 방식의 문제점은 메타데이터의 수명은 소켓의 수명과 연관되어 있는데, 개발자가 정확히 언제 소켓이 더 이상 프로그램에서 필요하지 않은지를 알고, Map에서 상응 매핑을 제거해야 한다는 것을 기억하지 못하면, Socket과 User 객체들은 Map에 영원히 머무르게 된다는 점이다. 이것 때문에 Socket과 User 객체들이 가비지 컬렉팅이 되지 못한다. 바로 이것이 꽤 오랫동안 메모리에서 프로그램이 실행되는 원인이 된다. 프로그램에서 더 이상 Socket이 필요하지 않을 때를 찾아내는 것은 수동적인 메모리 관리를 필요로 하므로 성가시면서도 에러를 일으킬 소지가 있는 기술이다.




메모리 누수 규명하기

프로그램이 메모리 누수 현상을 겪고 있다는 첫 번째 신호는 시스템이 OutOfMemoryError를 던지거나 빈번한 가비지 컬렉션으로 인해서 퍼포먼스가 나빠지기 시작하는 것이다. 다행히도 가비지 컬렉터는 메모리 누수를 진단할 때 사용할 수 있는 많은 정보들을 공유하고 있다. -verbose:gc 또는 -Xloggc 옵션으로 JVM을 호출하면 GC(garbage collection)가 실행하면서 진단 메시지를 콘솔 또는 로그 파일에 프린트 한다. 걸리는 시간, 현재 힙 사용, 복구된 메모리 등을 프린트한다. GC 사용을 기록하는 것은 그렇게 매력적인 일은 아니기 때문에 메모리 문제를 분석하거나 가비지 컬렉터를 튜닝해야 할 경우 제품에서 기본적으로 GC 로깅을 실행하는 것이 바람직하다.

툴들은 GC 로그 아웃풋을 취해 이를 그래픽으로 디스플레이 한다. 그 툴 중 하나가 프리 JTune(참고자료)이다. GC 후에 힙 사이즈의 그래프를 보면 프로그램의 메모리 사용 내용을 볼 수 있다. 대부분의 프로그램의 경우 메모리 사용을 두 개의 컴포넌트 (기본(baseline) 사용과 현재 로드(current load) 사용)로 나눌 수 있다. 서버 애플리케이션의 경우, 기본(baseline) 사용은 어떤 부하에도 종속되지는 않지만 요청을 받아들일 준비가 될 때 애플리케이션이 기본적으로 사용하는 것이다. 현재 로드 사용은 요청을 처리하는 프로세스에 사용되지만 요청 프로세싱이 완료되면 사라지는 것이다. 부하가 일정하면 애플리케이션은 일정하게 메모리를 사용한다. 애플리케이션이 초기화를 완료하고 부하가 증가하지 않는데도 메모리 사용 추세가 계속 상승되면, 이 프로그램은 아마도 이전 요청을 처리하는 과정에서 만들어진 객체를 보유하고 있는 것이 분명하다.

Listing 2는 메모리 누수가 있는 프로그램이다. MapLeaker는 쓰레드 풀에서 태스크룰 처리하고 각 태스크의 상태를 Map에 기록한다. 안타깝게도 태스크가 완료될 때 엔트리를 삭제하지 않기 때문에 상태 엔트리와 태스크 객체가(내부 상태와 함께) 영원히 축적된다.


Listing 2. Map 기반 메모리 유출을 가진 프로그램 
public class MapLeaker {
    public ExecutorService exec = Executors.newFixedThreadPool(5);
    public Map<Task, TaskStatus> taskStatus
         = Collections.synchronizedMap(new HashMap<Task, TaskStatus>());
    private Random random = new Random();
    private enum TaskStatus { NOT_STARTED, STARTED, FINISHED };
    private class Task implements Runnable {
        private int[] numbers = new int[random.nextInt(200)];
        public void run() {
            int[] temp = new int[random.nextInt(10000)];
            taskStatus.put(this, TaskStatus.STARTED);
            doSomeWork();
            taskStatus.put(this, TaskStatus.FINISHED);
        }
    }
    public Task newTask() {
        Task t = new Task();
        taskStatus.put(t, TaskStatus.NOT_STARTED);
        exec.execute(t);
        return t;
    }
}

그림 1은 GC 후에 MapLeaker에 대한 애플리케이션 힙 사이즈를 나타낸 그래프이다. 그래프가 계속 올라가고 있다는 것은 메모리 누수의 증거이다. (실제 애플리케이션에서, 이 슬로프는 이런 양상을 띄지는 않는다. 하지만 GC 데이터를 오랫동안 모으면 이런 모습이 된다.)


그림 1. 지속적으로 상승하는 메모리 사용
 

메모리 누수를 감지했다면 어떤 유형의 객체들이 문제를 일으키는지를 찾아낸다. 어떤 메모리 프로파일러로 객체 클래스로 나뉘어진 힙의 스냅샷을 만들 수 있다. 상용 힙 프로파일링 툴도 사용할 수 있지만 메모리 누수를 찾는데 돈까지 들일 필요는 없다. 빌트인 hprof 툴이 이 트릭을 수행한다. hprof를 사용하여 메모리 사용을 트래킹하도록 하려면 -Xrunhprof:heap=sites 옵션으로 JVM을 호출한다.

Listing 3은 애플리케이션의 메모리 사용 부분을 나타내는 hprof 아웃풋이다. 애플리케이션이 종료한 후 또는 Windows상에서 Ctrl+Break을 누르거나 kill -3으로 애플리케이션에 신호를 보낼 때 hprof 툴은 아웃풋을 만든다.

Listing 3보기. (Listing 3. Map.Entry와 Task 객체에서 증가 추세를 보이는 hprof 아웃풋)

Listing 4는 hprof 아웃풋의 또 다른 부분을 보여준다. Map.Entry 객체용 할당 사이트용 콜 스택 정보를 제공한다. 이 아웃풋은 어떤 호출 체인이 Map.Entry 객체들을 생성하고 있는지를 말해준다. 프로그램 분석과 더불어 이것은 매우 쉽게 메모리 누수의 원인을 알아낼 수 있는 방법이다.


Listing 4. Map.Entry 객체용 할당 사이트를 보여주는 HPROF 아웃풋 
TRACE 300446:
	java.util.HashMap$Entry.<init>(<Unknown Source>:Unknown line)
	java.util.HashMap.addEntry(<Unknown Source>:Unknown line)
	java.util.HashMap.put(<Unknown Source>:Unknown line)
	java.util.Collections$SynchronizedMap.put(<Unknown Source>:Unknown line)
	com.quiotix.dummy.MapLeaker.newTask(MapLeaker.java:48)
	com.quiotix.dummy.MapLeaker.main(MapLeaker.java:64)




약한 참조(weak reference)

SocketManager의 문제는 Socket-User 매핑의 수명이 Socket의 수명과 매치해야 하는데 이 규칙을 실행할 쉬운 방식을 제공하는 언어가 없다는 점이다. 프로그램은 수동 메모리 관리와 비슷한 기술에 의존하게 된다. 다행히도 JDK 1.2 부터, 가비지 컬렉터가 약한 참조(weak references)를 통해 이러한 유형의 메모리 누수를 방지하도록 한다.

약한 참조는 레퍼런트(referent)라고 하는 객체 레퍼런스를 위한 홀더(holder)이다. 약한 참조를 사용하여 레퍼런트에 대한 참조를 관리할 수 있다.(가비지 컬렉팅이 가능하다.) 가비지 컬렉터가 힙을 트레이스 할 때, 객체에 대한 대표적인 레퍼런스가 약한 참조라면 레퍼런트는 GC의 대상이 된다. (약한 참조에 의해 유일하게 참조되는 객체를 weakly reachable이라고 한다.)

약한 참조의 레퍼런트는 구현 시 설정되고 이 값이 제거되지 않았다면 이 값은 get()으로 검색된다. (레퍼런트가 이미 가비지 컬렉팅 되었기 때문이거나 누군가가WeakReference.clear()를 호출하여) 약한 참조가 제거되면 get()은 null을 리턴한다. 따라서 get()이 비 null 값을 리턴하는지 언제나 검사해야 한다. 레퍼런트는 결국 가비지 컬렉팅 될 것이기 때문이다.

일반 참조(strong reference)를 사용하여 객체 레퍼런스를 복사할 때 레퍼런트의 수명이 최소 복사된 레퍼런스의 수명만큼 되도록 제한한다. 객체에 대해 약한 참조를 구현하면 레퍼런트의 수명을 확장하지 않는다. 그저 이것이 살아있는 동안 만 관리하면 된다.

약한 참조는 약한 컬렉션(weak collection)들을 구현할 때 가장 유용하다. 이것은 정확히 SocketManager 클래스가 해야 할 일이다. 일반적인 약한 참조 사용이기 때문에, 키에 약한 참조를 사용하는 WeakHashMap도 역시 JDK 1.2에 있는 클래스 라이브러리에 추가되었다. 일반 HashMap에 키로서 객체를 사용하면 그 객체는 Map에서 매핑이 제거될 때 까지 컬렉팅 되지 않는다.WeakHashMap을 사용하여 객체가 가비지 컬렉팅 되는 것을 방지하지 않고도 Map 키로서 객체를 사용할 수 있다. Listing 5는 WeakHashMap에서의 get() 메소드의 가능한 구현이다. (약한 참조를 보여준다.)


Listing 5. WeakReference.get()의 구현 
public class WeakHashMap<K,V> implements Map<K,V> {
    private static class Entry<K,V> extends WeakReference<K>
       implements Map.Entry<K,V> {
        private V value;
        private final int hash;
        private Entry<K,V> next;
        ...    
    }
    public V get(Object key) {
        int hash = getHash(key);
        Entry<K,V> e = getChain(hash);
        while (e != null) {
            K eKey= e.get();
            if (e.hash == hash && (key == eKey || key.equals(eKey)))
                return e.value;
            e = e.next;
        }
        return null;
    }

WeakReference.get()이 호출되면(여전히 살아있다면) 레퍼런트에 대한 일반 참조를 리턴하기 때문에 while 루프의 바디에서 사라지는 매핑에 대해 걱정할 필요가 없다. 일반 참조는 가비지 컬렉팅 되는 것으로부터 이를 방지한다. WeakHashMap의 구현은 약한 참조를 가진 일반 이디엄을 의미한다. 몇몇 내부 객체는 WeakReference를 확장한다.

WeakHashMap에 매핑을 추가하면 키가 가비지 컬렉팅 되기 때문에 나중에 이 매핑은 빠질 수 있다. 결과적으로 get()은 null을 리턴하면서 null용 get()의 리턴 값을 테스트하는 것이 더 중요해진다.

누수와 WeakHashMap 연결하기

SocketManager 에서의 유출을 픽스하는 것은 쉽다. HashMap을 WeakHashMap으로 대체하면 된다.(Listing 6) (SocketManager가 쓰레드 보안이 되어야 한다면 WeakHashMapCollections.synchronizedMap()으로 래핑할 수 있다.) 매핑의 수명이 키의 수명과 연결되어야 할 때마다 이 방식을 사용할 수 있다. 하지만 이 기술을 남용하지 않도록 조심한다. 대부분의 경우에는 일반 HashMap은 올바른 Map 구현으로서 사용하는 것이 옳기 마련이다.


Listing 6. SocketManager를 WeakHashMap으로 픽스하기
public class SocketManager {
    private Map<Socket,User> m = new WeakHashMap<Socket,User>();
        public void setUser(Socket s, User u) {
        m.put(s, u);
    }
    public User getUser(Socket s) {
        return m.get(s);
    }
}

레퍼런스 큐 (Reference queues)

WeakHashMap은 맵 키를 보유하기 위해 약한 참조를 사용한다. 따라서 키 객체들이 애플리케이션에서 더 이상 사용되지 않을 때 이것을 가비지 컬렉팅 되도록 하고, WeakReference.get()null을 리턴하는지의 여부에 따라 get() 구현은 죽은 매핑과 살아있는 매핑을 구별한다. 하지만 이것은 get()의 메모리 사용이 애플리케이션의 수명 동안 늘어나는 것을 방지하는데 필요한 것의 절반 정도에 지나지 않는다. 어떤 것은 키 객체가 컬렉팅 된 후 Map에서 죽은 엔트리를 없애는데도 수행될 수 있다. Map은 상응하는 죽은 키들에 대한 엔트리들로 채운다. 그리고 이것이 애플리케이션에 보이지 않는 동안 Map.Entry와 값 객체가 컬렉팅되지 않도록 하기 때문에 메모리 밖에서 애플리케이션을 실행시키는 원인이 된다.

주기적으로 Map을 스캐닝하고, 각각의 약한 참조에 get()을 호출하고 get()이 null을 리턴하면 그 매핑을 제거하는 방식을 통해 죽은 매핑이 제거될 수 있다. 약한 참조의 레퍼런트가 가비지 컬렉팅될 때 공지가 될 수 있는 방법이 있다면 좋을 것이다. 바로 이 일을 레퍼런스 큐(reference queues)가 수행한다.

레퍼런스 큐는 정보를 객체의 라이프 사이클 정보를 애플리케이션에 피드백하는 가비지 컬렉터의 기본적인 수단이다. 약한 참조에는 두 개의 구조체가 있다. 하나는 인자로서 레퍼런트만 취하고 다른 하나는 레퍼런스 큐를 취한다. 약한 참조는 관련 레퍼런스 큐와 함께 생성되고 레퍼런트가 GC의 후보가 되면 레퍼런스 객체(레퍼런트가 아님)는 레퍼런스가 깨끗해진 후에 레퍼런스 큐에 인큐(enqueue) 된다. 이 애플리케이션은 레퍼런스 큐에서 레퍼런스를 검색하여 레퍼런트가 컬렉팅 되었다는 것을 알면 관련 클린업 액티비티를 수행할 수 있다. 약한 컬렉션에서 제거된 객체들의 엔트리를 삭제한다. (레퍼런스 큐는 BlockingQueue와 같은 큐 해제 모드를 제공한다.)

WeakHashMap은 대부분의 Map 연산 중에 호출되는 expungeStaleEntries()이라고 하는 프라이빗 메소드를 갖고 있다. 이것은 종료된 레퍼런스용 레퍼런스 큐를 폴링하고 관련 매핑을 제거한다. Listing 7에 expungeStaleEntries()의 구현이 나와있다. 키-값 매핑을 저장하는데 사용되는 Entry 유형은 WeakReference를 확장하여 expungeStaleEntries()가 그 다음의 종료된 약한 참조를 요청할 때 Entry를 가져온다. 콘텐트를 주기적으로 트롤링 하는 대신 Map을 청소하기 위해 레퍼런스 큐를 사용하는 것이 더 효율적이다. 살아있는 엔트리들이 클린업 프로세스에서 절대 건드리지 않기 때문이다. 실제로 인큐(enqueued) 레퍼런스가 있을 때만 수행된다.


Listing 7. WeakHashMap.expungeStaleEntries() 구현
    private void expungeStaleEntries() {
	Entry<K,V> e;
        while ( (e = (Entry<K,V>) queue.poll()) != null) {
            int hash = e.hash;
            Entry<K,V> prev = getChain(hash);
            Entry<K,V> cur = prev;
            while (cur != null) {
                Entry<K,V> next = cur.next;
                if (cur == e) {
                    if (prev == e)
                        setChain(hash, next);
                    else
                        prev.next = next;
                    break;
                }
                prev = cur;
                cur = next;
            }
        }
    }




결론

약한 참조와 약한 컬렉션은 힙 관리에 있어 강력한 툴이다. 애플리케이션이 일반 참조의 "모 아니면 도" 식의 접근이 아닌 보다 세련된 개념의 접근 방식을 사용할 수 있기 때문이다. 다음 달에는 소프트 레퍼런스(soft references)를 연구해 보자


Posted by 삼스
Android/디바이스2010. 5. 4. 10:57

출처 : http://www.androidpub.com/307179


1. SD 카드 파티션 나누기 (fat32 / ext2로 나눔)

ext2 파티션은 500메가 정도가 적당합니다.

윈도우에서 하실분은 파라곤 파티션 메니져 프로그램으로 가능합니다.

 

2. 루팅 및 busybox 설치

 

3. 마운트 후 어플을 SD 카드로 옮기기

 

ex) 깔려있는 모든 어플을 SD카드로 옮길때

busybox mount -t ext2 /dev/block/mmcblk0p2 /system/sd

busybox cp -a /data/app /system/sd

busybox rm -rf /data/app

busybox ln -s /system/sd/app /data/app

 

4. 이후 재 부팅시에는 마운트를 다시 해줘야함.

부팅 지연 시는 usb 연결 후 쉘에서 수동 마운트 하면됩니다.


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

수동마운트가 좀 걸리긴 하지만 한번 설정하고 계속 충전하면서 쓰면 별 문제 없을 듯 합니다.


Posted by 삼스
Android/App개발2010. 4. 28. 15:05
아래의 순서로 작성하면 됨.
1. View나 View subclass를 확장
2. 필요한 메소드들을 오버라이드

오버라이드할 메소드 리스트
1. 생성관련
  생성자 : code상으로 생성되는 생성자와 layout파일로부터 inflate되면서 생성되는 경우에 호출되는 생성자 두가지가 있으며 두번째 생성자는 layout파일에 정의되어 있는 속성들을 파싱하여 적용하는 부분이 추가로 코딩되어야 한다.
  onFinishInflate : layout XML파일로부터 뷰와 그의 모든 차일드들이 inflate된 후에 호출됨. 이 때 추가로 어떤 작업을 하고자 하는 경우 여기서 하면 된다.
2. Layout관련
  onMeasure(int, int) : 부모에서 계산된 width, height 스펙값내에서 뷰와 차일드들의 크기를 계산하여 적용한다.
  onLayout(boolean, int, int, int, int) : 뷰가 size와 postion을 모든 차일드에 지정해야 할경우 호출된다.
  onSizeChanged(int, int, int, int) : 뷰의 size가 변경된 후 호출된다.
3. 그리기관련
  onDraw(Canvas) : View가 내용을 다시 그려야 할 때 호출된다.
4. Event 처리
  onKeyDown(int, KeyEvent) : 새로운 key event가 발생하였을 때 호출된다.
  onKeyUp(int, KeyEvent) : key up event가 발생하였을 때 호출된다.
  onTrackballEvent(MotionEvent) : 트랙볼 모션이벤트가 발생하였을 때 호출된다.
  onTouchEvent(MotionEvent) : 터치스크린의 모션이벤트가 발생하였을 때 호출된다.
  onFocusChanged(boolean, int, Rect) :  View가 포커스를 얻거나 잃었을 때 호출된다.
  onWindowFocusChanged(boolean) : View를 포함하고 있는 윈도우가 포커스를 얻거나 잃었을 때 호출된다.
5. Attach관련
  onAttachedToWindow() : View가 Window에 attach되었을 때 호출된다.
  onDetachedFromWindow() : View가 Window로부터 detach되었을 때 호출된다.
  onWindowVisibilityChanged(int) : View를 포함하는 window의 visibility가 변경되었을 때 호출된다.



Posted by 삼스
Android/App개발2010. 4. 26. 16:25
http://www.androidpub.com/?mid=android_dev_info&category=127161&document_srl=180463
-> 안드로이드 펍에서 가져온 문서입니다.

터치 모드

안드로이드용 사용자 인터페이스를 디자인하고 개발하는 것은 일반적인 데스크탑 환경에서 하는 것과는 상당한 차이가 있습니다.  안드로이드는 모바일 장치에서 어플리케이션을 실행하고, 어플리케이션 디자이너와 개발자들은 명확하지 않은 다수의 제약사항을 다뤄야 하기 때문입니다.  우리는 여러분이 더 나은 어플리케이션을 디자인하고 개발하는 것을 돕기위해 안드로이드 사용자 인터페이스에 관한 새로운 시리즈의 게시물을 작성하고자 합니다.  이 시리즈에서 우리는 여러분에게 디자인 가이드, 툴, 개발팁을 제공하고 안드로이드 UI 툴킷의 핵심적인 원리들을 설명할 것입니다.  목표는 간단합니다: 우리는 여러분이 훌륭한 사용자 경험(인터페이스)을 디자인하고 개발하는 것을 돕고 싶습니다.  이 시리즈를 시작하면서, UI 툴킷의 가장 중요한 원리 중 하나인 터치 모드에 대해 소개하고자 합니다. 터치 모드는 폰과 사용자의 접촉에 따른 뷰 계층의 상태입니다.  터치 모드는 마지막 사용자 접촉이 터치 스크린에 실행됐는지 간단히 표시하기 때문에, 그 자체만으로도 아주 이해하기 쉬운 것입니다.  예를 들어, 여러분이 G1 폰을 사용하고 있다면, 틀랙볼로 위젯을 선택하는 것이 여러분을 터치 모드에서 빠져나오게 할 것입니다; 하지만, 여러분이 스크린의 버튼을 손가락으로 접촉하면, 여러분은 터치 모드로 들어오게 됩니다. 사용자가 터치 모드가 아닐 때, 우리가 틀랙볼 모드, 네비게이션 모드 혹은 키보드 네비게이션이라고 말할 때 이 용어들에 놀라지 마시기 바랍니다.  마지막으로, 터치 모드와 직접 관련된 단 하나의 API만이 존재합니다.

View.isInTouchMode(). 쉽죠?  이상하리만큼, 터치 모드는 거짓말처럼 간단하지만 터치 모드에 들어감으로 인해 생기는 결과는 여러분이 생각하는 것보다 훨씬 큽니다. 몇 가지 이유에 대해 알아봅시다.


터치 모드, 선택, 그리고 Focus

모 바일 장치용 UI 툴킷을 디자인하는 것은 모바일 장치가 제공하는 다양한 상호작용 메카니즘으로 인해 매우 어렵습니다. 일부 장치들은 12개의 키만 제공하고, 어떤 장치는 터치 스크린이 있고, 어떤 장치는 스타일러스가 필요하고, 어떤 장치는 터치 스크린과 키보드 둘 다 가지고 있습니다.  이런 상황에서, 최초의 상용 장치 G1이 터치 스크린, 트랙볼, 키보드를 사용하는 다중 입력폼을 제공한다는 사실은 안드로이드 개발 커뮤니티에 긍정적인 영향을 줍니다.  사용자가 세 가지의 다른 메카니즘을 통해 어플리케이션과 소통할 수 있기 때문에, 우리는 가능한 모든 상황을 심각하게 고려해야만 합니다.  우리는 한 가지 문제 때문에 터치 모드를 만들게 되었습니다.
텍스트 아이템 리스트를 보여주는 간단한 어플리케이션(예) 
ApiDemos )을 생각해 보세요. 사용자는 틀랙볼을 사용해서 자유롭게 리스트를 볼 수 있고, 또한 스크롤 하거나 손가락을 이용해 화면을 이동할 수도 있습니다. 이 시나리오에서 선택이 문제가 됩니다.  만약, 제가 리스트에 있는 최상단의 아이템을 선택하고 아래로 쓸어내리면, 선택의 관점에서는 어떤 일이 일어나야 할까요?  아이템이 그대로 선택되어져 있고 화면만 스크롤 되어야 할까요?  이 상황에서, 제가 트랙볼을 이용해 선택위치를 옮기기로 결정한다면 무슨 일이 일어날까요?  혹은 더 심하게, 만약 제가 화면에 더 이상 보이지 않는 현재 선택된 아이템을 실행하기 위해 트랙볼을 누른다면?  신중하게 생각한 후, 우리는 선택을 해젷기로 결정했습니다.  

터 치 모드에서는 Focus도 선택도 없습니다.  사용자가 터치 모드에 들어서자마자, 그리드 안의 리스트에 있는 선택 아이템은 선택해제 됩니다. 마찬가지로, 사용자가 터치 모드에 들어서면, 선택된 위젯도 선택해제 됩니다. 아래 이미지는 사용자가 틀랙볼로 아이템을 선택한 후 리스트를 터치하면 어떻게 되는지 보여줍니다.



프레임 워크는 사용자가 좀 더 자연스러움을 느끼게 하기 위해, 터치 모드를 벗어나면 기존의 선택/Focus 상태를 복원할 수 있습니다.  예를 들어, 위의 예제와 같이, 사용자가 트랙볼을 다시 사용해야 한다면, 이전에 선택된 아이템이 다시 선택되어 집니다. 이것이 왜 일부 개발자들이 언제 커스텀 뷰를 생성하고 트랙볼을 한번 움직인 다음부터 키 이벤트를 받기 시작하는 것을 혼동하는 이유입니다: 어플리케이션은 터치 모드에 있고, 터치 모드를 나가 Focus를 복원 하기 위해 트랙볼을 사용해야 합니다.

터치 모드, 선택, Focus의 관계는 여러분이 여러분의 어플리케이션을 빠져나가기 위해 선택과(혹은) Focus에 의존해서는 안된다는 뜻입니다.  새내기 안드로이드 개발자들이 가지는 일반적인 문제는 
ListView.getSelectedItemPosition()에 의존하는 것입니다. 터치 모드에서 이 메서드는 INVALID_POSITION을 리턴할 것입니다. 여러분은 click listeners 혹은 choice mode를 사용해야 합니다.


터치 모드에서 focusable

이 제 여러분은 터치 모드에서 Focus가 존재하지 않는다는 것을 압니다. 하지만 저는 이것이 100% 사실은 아니라는 것을 설명해야 합니다. Focus는 터치 모드에 존재할 수 있지만, 아주 특별한 방법으로만 가능합니다.  우리는 이것을 터치 모드의 focusable이라고 부릅니다. 이 특별한 모드는 EditText와 같은 텍스트 입력을 받는 위젯이나 ListView에 서 필터링이 활성화 되었을 때를 위해 만들어졌습니다. 이것이 왜 사용자가 트랙볼이나 손가락으로 먼저 선택하지 않고도 텍스트 필드에 텍스트를 입력할 수 있는 이유입니다. 사용자가 스크린을 터치할 때, 어플리케이션이 이미 터치 모드가 아니면, 터치 모드로 들어갑니다. 터치 모드로 들어가는 과정에서 생기는 일은 사용자가 무엇을 터치했는지와 현재 Focus의 상태에 따라 다릅니다. 사용자가 터치 모드의 focusable 상태인 위젯을 터치하면, 위젯이 Focus가 됩니다.  그 외에 터치 모드의 focusable 상태가 아닌 한, 현재 Focus된 어떤 위젯도 Focus되지 않습니다.  예를 들어, 아래의 이미지에서, 사용자가 스크린을 터치하면, 입력 텍스트 필드가 Focus가 됩니다.



터 치 모드의 focusable은 여러분이 코드나 XML에서 스스로 설정할 수 있는 속성입니다. 그러나, 이 속성이 안드로이드의 일관된 일반적인 습성을 훼손하기 때문에, 최소한으로 그리고 아주 특별한 상황에서만 사용되어야 합니다. 터치 모드의 focusable 속성을 잘 사용할 수 있는 게임은 좋은 어플리케이션 예제입니다. 구글 맵에서처럼, 풀스크린에서 사용되는 MapView는 터치 모드의 focusable을 올바로 사용할 수 있는 또 다른 좋은 예제입니다. 

아래는 터치 모드의 focusable 위젯을 사용하는 다른 예입니다.  사용자가 AutoCompleteTextView의 제안을 손가락으로 선택하면, Focus가 텍스트 입력 필드에 남아있습니다:



새내기 안드로이드 개발자들은 종종 터치 모드의 focusable이 선택/Focus가 사라지는 문제를 "고치는" 해결책이라고 생각합니다. 우리는 여러분이 이것을 사용하기 전에 매우 신중히 생각해 보기를 권유드립니다. 만약 잘못 사용되면, 이것은 여러분의 어플리케이션이 시스템의 나머지 다른 것들과 다르게 행동하게 만들며, 사용자의 습관을 무시하게 됩니다. 안드로이드 프레임워크는 "터치 모드의 focusable"을 사용하지 않고 사용자와의 상호작용을 다룰 수 있는 모든 도구를 포함하고 있습니다. 예를 들어, 선택상태를 유지하는 ListView를 만드는 대신, 간단히 적당한 
choice mode를 사용합니다. 만약 여러분이 프레임워크가 여러분의 필요를 충족시키지 못한다고 느낀다면, 우리에게 알려주시거나 패치를 제공해 주시기 바랍니다.

터치 모드 컨닝 페이퍼

해야할 것:

  • 핵심 어플리케이션과 일관성을 유지하라
  • 지속적인 선택상태가 필요하면, 적절한 기능을 사용하라 (라디오 버튼, 체크 박스, ListView의 선택 모드 등)
  • 게임을 개발한다면, 터치 모드의 focusable을 사용하라

하 지 말아야 할 것:

터치 모드에서 Focus나 선택을 유지하려고 하지 말라

Posted by 삼스
Android/App개발2010. 4. 26. 14:14
http://www.androidpub.com/android_dev_info/256251 <- AndroidPub의 휴우 님의 글을 가져온것입니다.


Layout Tricks: Creating Reusable UI Components
 안드로이드 플랫폼은 다양한 종류의 단순한 UI 위젯을 제공하며, 개발자는 이 UI 위젯들을 조합하고 이어 붙여 복잡하고 유용한 UI를 만들수 있다. 그러나 어플리케이션을 개발하다 보면, 작은 UI 블럭이 아닌 그 보다 좀 더 큰  보다 상위의 UI 구성요소가 필요 할 때가 있다. 이러한 요구사항을 효율적으로 수행하기 위해, 개발자는 여러개의 표준 UI 위젯들을 하나의 재사용가능한 구성요소로 결합해 사용할 수 있다.

 예를들어, 개발자는 다음과 같은 상위 UI 블럭들을 만들어 낼 수 있다. - 하나의 프로그레스바와 취소 버튼으로 이루어진 블럭, OK 와 Cancel 두 개의 버튼을 포함한 패널, 아이콘과 제목, 짧은 설명을 곁들인 패널등등. 이러한 상위 UI 요소들을 Custom View 이용해 코드를 작성해 만들 수도 있지만, 보다 손쉽게 오직 XML 만을 이용해서 만들 수도 있다.

 안드로이드 XML 레이아웃 파일 내에서, 각각의 태그는 실재의 클래스 Instance 와 매핑될 수 있지만, (이 때 클래스들은 항상 View 의 서브클래스이다.)  UI 툴킷은  View 클래스 Instance 와 매핑되지 않는 세 가지 특별한 태그를 제공해 준다. 그 태그는 <requestFocus />, <merge /> 그리고 <include /> 이다. 그 중에서 이 글은, 순수하게 XML 으로 구성된 UI 구성요소를 만들기 위해 <include /> 태그를 어떻게 사용하는지 설명하고 있다. <include/> 와 결합해서 사용할 경우 매우 강력한,<merge /> 태그를 사용하는 방법에 관해서는 Merging Layouts 문서를 참고하라.

 <include /> 태그는 이름이 뜻하는 그대로의 역할을 수행한다. - 또다른 XML 레이아웃을 포함할 수 있게 해준다. 이 태그를 사용하는 것은 Home 어플리케이션에서 발췌한 다음의 예제에서 볼 수 있듯이 매우 단순한다. 

<com.android.launcher.Workspace
   
android:id="@+id/workspace"
   
android:layout_width="fill_parent"
   
android:layout_height="fill_parent"
   
launcher:defaultScreen="1">

 
<include android:id="@+id/cell1" layout="@layout/workspace_screen" />
 
<include android:id="@+id/cell2" layout="@layout/workspace_screen" />
 
<include android:id="@+id/cell3" layout="@layout/workspace_screen" />

</com.android.launcher.Workspace>

 <include /> 에서 반드시 요구되는 속성 값은 layout  속성 뿐이다. 이 속성은 android namespace prefix 없이 사용되며, 추가하고자 하는 layout 파일을 참고하는데 사용된다. 위 예제에서는 <include/> 태그를 이용하여, 동일한 layout (@layout/workspace_screen)을 세 번 연속하여 포함 시켰다. 이 Tag는 또한 개발자가 속성값을 Override 할 수 있게 해준다. 위 예제에서 처럼 android:id 속성을 이용하여, 레이아웃을 포함하고있는 Root View 의 id 값을 설정 할 수도 있고, 마찬가지로 포함되어진 레이아웃들의 id 값을 설정할 수도 있다. 이와 유사하게, 개발자는 포함시킨 레이아웃의 또 다른 Layout 파라메터들도 (android:layout_ 으로 시작되는 모든 속성 값) Override 할 수 있다. 여기 몇 가지 예가 있다.

<include android:layout_width="fill_parent" layout="@layout/image_holder" />
<include android:layout_width="256dip" layout="@layout/image_holder" />

 이 태그는 개발자가 장치의 설정 변경에 따라(예를 들어 화면 가로 세로...) UI 의 일부분이 변경되도록 UI를 커스터마이즈 하고자 할 때 유용하게 사용된다. 그런 경우, 엑티비티의 메인 레이아웃은 layout/ 디렉토리에 위치하고, layout-land/ 과layout-port/ 폴더에 두 가지 서로 다른 형태의 레이아웃(하지만 이름은 동일한...)을 메인 레이아웃에서 포함하도록 구현 할 수 있다. 이러한 방식은 개발자가 포트레이트 모드와 랜드스케이프 모드 화면을 구성하는데 있어서, 대부분의 UI 요소를 공유해서 사용할 수 있도록 해준다.

Layout Tricks: Creating Efficient Layouts


  안드로이드 UI 툴킷은 몇 가지 Layout 매니저(LinearLayout / RelativeLayout 등의 ViewGroup)를 제공한다. Layout 매니저들은 쉽게 사용할 수 있으며, 많은 경우 기본적인 기능만을 이용해서 원하는 사용자 인터페이스를 구현할 수 있다. 

 하지만 불행하게도, 기본적인 구성요소만을 사용하는 것이 사용자 인터페이스를 작성하는데 가장 효율적인 방법은 아니다. 예를 들어, 개발자가 LinearLayout 을 남용해서 사용할 경우, 전체 UI를 구성하는 View의 계층구조가 복잡해지고, 사용되는 View 의 숫자가 크게 증가될 수 있다. 화면을 구성하는데 사용된 모든 View 와 Layout 매니저들을 처리하는데는 비용이 든다. 특히 LinearLayout 의 weight 파라매터를 사용하는 경우, Layout 화면을 구성하기 위해,  두 번의 Measure Pass 를 거쳐야 하기 때문에 더욱 많은 비용이 소모된다.

 여기 매우 단순하고 일반적인 예가 하나 있다. 아래의 그림과 같이 하나의 리스트 아이템을 표현하기 위한 Layout 을 생각해 보자. 왼쪽에는 아이콘 이미지가 있고, 상단에는 타이틀 텍스트가 그 아래는 부가적인 설명이 포함된 Layout 이다. 


 HierarchyViewer 로 캡처한 Layout 와이어 프레임을 살펴보면, 하나의 ImageView 와 두 개의 TextView 가 어떻게 구성되어 있는지 보다 명확하게 확인 할 수 있다. 


 LinearLayout 을 이용하 이러한 화면을 구성하는 것은 어렵지 않다. 전체 Layout 은 Horizontal LinearLayout (orientation 속성값이 Horizontal ) 이며, 하나의 ImageView 와 Vertical LinearLayout 을 포함하고 있다. 또한 Vertical LinearLayout 은 두 개의 TextView 를 포함한다. 소스 코드는 다음과 같다.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   
android:layout_width="fill_parent"
   
android:layout_height="?android:attr/listPreferredItemHeight"
   
   
android:padding="6dip">
   
   
<ImageView
       
android:id="@+id/icon"
       
       
android:layout_width="wrap_content"
       
android:layout_height="fill_parent"
       
android:layout_marginRight="6dip"
       
       
android:src="@drawable/icon" />

   
<LinearLayout
       
android:orientation="vertical"
   
       
android:layout_width="0dip"
       
android:layout_weight="1"
       
android:layout_height="fill_parent">

       
<TextView
           
android:layout_width="fill_parent"
           
android:layout_height="0dip"
           
android:layout_weight="1"
                   
           
android:gravity="center_vertical"
           
android:text="My Application" />
           
       
<TextView  
           
android:layout_width="fill_parent"
           
android:layout_height="0dip"
           
android:layout_weight="1"
           
           
android:singleLine="true"
           
android:ellipsize="marquee"
           
android:text="Simple application" />
           
   
</LinearLayout>

</LinearLayout>

 이 Layout 은 정상적으로 작동하지만, 낭비 요소를 담고 있다. 개발자는 하나의 RelativeLayout 이용해서 동일한 형태의 UI를 구성할 수도 있다. 그럼으로 화면을 구성하는데 사용되는 View 의 수를 하나 줄일 수 있고, 추가 적으로 View 의 계층 구조도 좀 더 단순화 될 수 있다. 또한 RelativeLayout 을 이용해서 구현하는 것은 어렵지도 않다.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   
android:layout_width="fill_parent"
   
android:layout_height="?android:attr/listPreferredItemHeight"
   
   
android:padding="6dip">
   
   
<ImageView
       
android:id="@+id/icon"
       
       
android:layout_width="wrap_content"
       
android:layout_height="fill_parent"
       
       
android:layout_alignParentTop="true"
       
android:layout_alignParentBottom="true"
       
android:layout_marginRight="6dip"
       
       
android:src="@drawable/icon" />

   
<TextView  
       
android:id="@+id/secondLine"

       
android:layout_width="fill_parent"
       
android:layout_height="26dip"
       
       
android:layout_toRightOf="@id/icon"
       
android:layout_alignParentBottom="true"
       
android:layout_alignParentRight="true"
       
       
android:singleLine="true"
       
android:ellipsize="marquee"
       
android:text="Simple application" />

   
<TextView
       
android:layout_width="fill_parent"
       
android:layout_height="wrap_content"
       
       
android:layout_toRightOf="@id/icon"
       
android:layout_alignParentRight="true"
       
android:layout_alignParentTop="true"
       
android:layout_above="@id/secondLine"
       
android:layout_alignWithParentIfMissing="true"
               
       
android:gravity="center_vertical"
       
android:text="My Application" />

</RelativeLayout>

  새로운 구현 방식은 이전에 LinearLayout을 이용한 구현한 것과 거의 동일하게 작동하지만, 한가지 주의해야할 점이 있다. 위의 Layout 중에, 만일 특정 리스트 아이템에 대해서, 부가적인 설명을 나타내는 TextView 가 사용될 필요가 없는 경우 이전 LinearLayout 을 이용해서 구성한 경우, 해당 TextView 의 Visibility 속성을 Gone 으로 설정하면 손쉽게 부가 설명 항목을 제외 시킬 수 있다. 하지만 그러한 방식은, RelativeLayout 을 사용하는 경우 정상적으로 작동하지 않을 수도 있다.

 RelativeLayout 에서 View 들은 특정 부모 View (RelativeLayout 자체나 아니면 해당 Layout이 참조하고 있는 View) 를 기반으로 정렬되어 진다. 예를 들어, 위 예제의 경우, 부가적인 설명을 표시하는 TextView 는 RelativeLayout 의 바닦면에 정렬되며, 타이틀을 표시하는 TextView 는 그 View 위에 위치하도록 선언되어 있다. 이 때, 부가적인 설명을 표시하는 TextView 가 Gone 이 될 경우, RelativeLayout 은 타이틀을 표시하는 View 를 어디에 위치시켜야 할지 알 수 없게 된다. 이러한 문제를 해결하기 위해, 개발자는 layout_alignWithParentIfMissing 파라매터를 사용할 수 있다. 

 이 Boolean 인자는 만일 특정한 View 가 기준으로 삼아야되는 부모 View 가 없을 경우, 해당 View 를 포함하고 있는 RelativeLayout 자체를 기준으로 삼으라고 알려준다.  예제의 경우, aliginWithParentIfMissing 를 설정함으로서, 만일 타이틀을 표시하는 TextView 가 기준으로 삼고 있는 TextView 가 없을 경우 (Gone 에 의해...), 해당 View 대신 RelativeLayout 을 기준으로 삼게 되고, 그 결과 TextView 는 아래와 같이 RelativeLayout 의 바닦면 바로 위에 위치하게 된다.

 이로서 RelativeLayout 을 이용해 구성한 두 번째 UI도 완벽하게 작동한다. 무엇보다도, 계층구조는 더 단순해 졌고, LinearLayout 의 weight 파라메터를 사용하지 않았기 때문에 더 효율적이다. 두 가지 구현 방식의 차이점은 HierarchyViewer 를 이용해 살펴 보면 확실하게 들어난다.

 다시한번 말하지만, 이러한 차이는 ListView 를 구성하는 개발 아이템 아이템 마다, 동일한 형식의 Layout 을 사용하게 되면 훨씬 더 중요해 질 수 있다.  개발자들이 이번 글에서 예로든 간단한 예제를 통해, 여러가지 레이아웃에 대해 잘 아는 것이, UI 를 최적화하는데 최선의 길임을 알 수 있기를 희망한다.

Layout Tricks: Using ViewStubs

 안드로이드에서 Activity 간에 UI 구성 요소를 공유하고 재사용하는 것은 매우 쉽다. <include/> 태그에게 감사하자. 때로는 복잡한 UI를 만들어내는 것이 너무 쉬운 나머지, 매우 드물게 사용되는 View 들도 전부 포함하는,  굉장히 많은 View 로 구성된 UI를 만들어 낼 수도 있다. 이런 경우를 대비해, 매우 고맙게도, 안드로이드에서는 ViewStub 위젯을 제공해 준다. ViewStub 을 사용하면,  개발자는 <include/>  태그가 제공하는 이점을 취하면서도, 잘 사용되지 않는 View 는 생성하지 않고 Layout 을 구성할 수 있다.

 ViewStub 은 매우 가벼운 더미 View 이다. 이 View 는 어떤 영역을 차지하지도 않으며, 무엇을 그리지도 않고, UI 를 구성하는 Layout 에 참여하지도 않는다.  즉, ViewStub 을 생성하고, View 계층구조내에서 유지하는데 매우 적은 비용만이 필요하다.  ViewStub 은 Lazy Include 하다고 표현할 수 있는데,  ViewStub 에 의해 참조되고 있는 View 는 오직 개발자가 명시적으로 지시하는 경우에 한해서, 생성(inflate)되고 View 계층 구조에 추가되기 때문에 그렇다. (즉, Layout Inflate 시점에 바로 생성되지 않고 그 이 후에 생성된다...)

 다음 그림은 Shelves 어플리케이션의 스크린 샷이다. 이 Activity 는 책 선 반위에 놓인, 사용자들이 열람가능한 책 목록을 보여준다. 


 동일한 Activtriy 가 사용자가 새로운 책을 추가할 때도 사용된다. 그런데, 책을 추가하는 작업이 이루어지는 동안에, Shelves 는 추가적인 사용자 인터페이스를 보여준다. 아래의 스크린샷에서 볼 수 있듯이, 책을 추가하는 동안 화면 하단에,  프로그래스바와 취소 버튼이 나타난다. 


 책을 추가하는 작업은, 적어도 책 목록을 살펴보는 작업에 비해 일반적인 작업이 아니기 때문에,  해당 정보를 표시하는 패널은 ViewStub 을 이용하여 구현되어 있다.


 사용자가 책을 추가하는 작업을 시작하는 시점에 ViewStub 이 실재로 형상화 되며, 해당 ViewStub 이 참조하고 있는 Layout 파일이 ViewStub 을 대체하게된다.


  ViewStub 을 사용하기 위해서, 개발자는 ViewStub 을 실재로 형상화 하기 위해 필요한 android:id 속성 값과 해당 ViewStub 이 어떤 Layout 파일에 정의된 View 와 교체될지를 나타내는 android:layout 속성 값을 지정해 주면된다. 추가로, ViewStub 에서는 또 한 가지 속성값이 사용된다. android:inflatedId 는 참조되는 Layout 파일의 루트 View 의 android:id 속성 값을 Override 할 수 있게 해 준다. 또한, ViewStub 에 정의한 Layout 파래매터들은, ViewStub 이 참조한 Layout 의 루트 View 에 적용된다. 실재 사용예는 아래와 같다.

<ViewStub
 
android:id="@+id/stub_import"
 
android:inflatedId="@+id/panel_import"

 
android:layout="@layout/progress_overlay"

 
android:layout_width="fill_parent"
 
android:layout_height="wrap_content"
 
android:layout_gravity="bottom" />

  개발자가 ViewStub 을 형상화 하고자 할 때면, 단순히 해당 ViewStub 의 inflate()메서드를 호출 하거나, Visibility 속성을 VISIBLE 이나 INVISIBILE 로 변경하면 된다. 단, inflate() 메서드를 를 호출할 경우, 형상화된 Layout 파일의 루트 View 를 반환값으로 전달 받을 수 있는 장점이 있다. 

((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
// or
View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();

  ViewStub 이 형상화된 이 후에는, View 계층 구조에서 완전히 제거된다는 것을 기억하는 것은 매우 중요하다. 즉, ViewStub 에 대한 참조를 Class 멤버 변수로 두는 식으로 오래 동안 유지할 필요가 없다. 

  ViewStub 은 쉬운 프로그래밍과 효율적인 프로그래밍 사이에서 환상적인 조화를 이루고 있다. 어플리케이션 실행 중에, 특정한 View 를 코드상에서 생성한 후, View 계층 구조에 추가하도록 구현하기 보다 ViewStub 을 사용하는 것이 훨씬 쉽고 효율적일 수 있다. 현재 ViewStub 이 갖고 있는, 딱 한가지 단점은 <merge/> 태그를 지원하지 않는다는 것이다. 

 

Layout Tricks: Merging Layouts

  한 번 작성한 Layout 코드를 공유하고 재사용하기 위해 <include/> 태그를 사용하는 방법에 대하여 이야기 했었다. 이번 글에서는 <include/> 태그를 사용할 때 생기는 문제점을 보완해 줄 수 있는 <merge/> 태그에 관해 이야기해 본다.

 안드로이드에서 UI Layout 을 구성 할 때, View 계층 구조의 단계를 줄여 최적화 하기 위해 <merge /> 태그가 만들어졌다.  예제를 통해 살펴보면, 이 태그를 사용하는 목적에 대해 쉽게 이해할 수 있다. 다음의 예는, 어떤 이미지를 표시하고, 그 이미지 위해 해당 이미지의 제목을 표시해 주는 XML Layout 이다. 구조는 매우 단순하다.  FrameLayout 을 이용해서, ImageView 위에 TextView 를 표시하였다. 

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   
android:layout_width="fill_parent"
   
android:layout_height="fill_parent">

   
<ImageView  
       
android:layout_width="fill_parent"
       
android:layout_height="fill_parent"
   
       
android:scaleType="center"
       
android:src="@drawable/golden_gate" />
   
   
<TextView
       
android:layout_width="wrap_content"
       
android:layout_height="wrap_content"
       
android:layout_marginBottom="20dip"
       
android:layout_gravity="center_horizontal|bottom"

       
android:padding="12dip"
       
       
android:background="#AA000000"
       
android:textColor="#ffffffff"
       
       
android:text="Golden Gate" />

</FrameLayout>

 이 Layout 은 원하는 대로 잘 작동하며, 특별히 잘못된 점은 찾을 수 없다.

 하지만 만일 이 레이아웃을 HierarchyViewer 를 통해 살펴보면 흥미로운 점이 들어난다. 개발자가 View 계층 구조를 잘 살펴보면, 우리가 XML 파일에 정의한  FrameLayout 이 딱 하나의  FrameLayout 만을 자식 View 로 가지고 있는 것을 확인 할 수 있다.  (파란색으로 강조되어 있다.)

 이 때 FrameLayout 은 fill_parent 속성 값을 사용하고 있기 때문에, 그 부모와 동일한 영역을 차지한다. 또한, 특별한 배경을 지정하지도 않았으며, 추가적인 Padding 속성이나 Gravity 속성을 지정하지 않았기 때문에, 사실 화면을 구성 하는데 역할을 수행하지 않는다. 즉, 이 경우 추가적으로 사용된 FrameLayout 은 어떠한 이유도 없이 그저 UI 를 보다 복잡하게 만들 뿐이다. 하지만  우리가 어떻게 이 FrameLayout 을 제거할 수 있을까?  어찌되었든, Layout 을 지정하는 XML 다큐먼트는 Root 태그를 가져야 하면,  XML 상에 정의된 태그 는 실제View 로 구현된다.

 바로 이 경우에 <merge/> 태그가 쓸모있다.  LayoutInflater 가 View 를 형상화 하는 과정 중에, <merge/> 태그를 만나게 되면, 해당 <merge/> 태그는 건너 뛰고,  그 자식 View 들을 <merge/> 태그의 부모 View 에 추가한다. 설명이 조금 헷갈릴 수도 있겠다. 이해를 돕기 위해, 이전 예제에서 사용된 FrameLayout  대신 <merge/> 를 사용한 후 살펴 보자. 

<merge xmlns:android="http://schemas.android.com/apk/res/android">

   
<ImageView  
       
android:layout_width="fill_parent"
       
android:layout_height="fill_parent"
   
       
android:scaleType="center"
       
android:src="@drawable/golden_gate" />
   
   
<TextView
       
android:layout_width="wrap_content"
       
android:layout_height="wrap_content"
       
android:layout_marginBottom="20dip"
       
android:layout_gravity="center_horizontal|bottom"

       
android:padding="12dip"
       
       
android:background="#AA000000"
       
android:textColor="#ffffffff"
       
       
android:text="Golden Gate" />

</merge>

  이 새로운 버전의 Layout 에서 TextView 와 ImageView 는  최상위 FrameLayout 에 바로 추가된다. 그 결과, 화면상으로 동일하게 보이지만, 실재 View 계층구조는 좀 더 단순해 진다.

  명백하게도  위의 예제에서 <merge /> 태그를 사용 할 수 있는 것은, Activity 에 사용된  Content View 가 FrameLayout  이기 때문이다. (FrameLayout 이 두 번 반복되기 때문에 하나를 줄일 수 있음)  만일 FrameLayout 대신 LinearLayout 이 Root  태그로 사용되었다면, <merge/> 태그를 사용할 수 없다. 

  하지만 <merge /> 태그는 다른 경우에도 유용하게 사용될 수 있다. 예를 들어, <merge /> 태그는 <include /> 를 통해 View 를 추가하고자 할 때, 완벽하게 작동한다. 또한, XML 상에서 몇개의 View 를 조합하여, 커스텀한 View 를 구성하고자 하는 경우에도 <merge />  태그는 유용하게 사용된다.  Button 내부의 내용을 원하는 대로 수정할 수 있는, 두 개의 Button 을 보여주는 OKCancelBar 라는 CustomView 를 만들 때,  <merge /> 를 어떻게 사용할 수 있는지 한 번 살펴보자. (원한 다면, 이 예제의 완벽한 소스를 다운로드 받을 수도 있다.)  아래의 예제는,  어떤 이미지 위에 우리가 새롭게 정의한 CustomView 를 표시한다. 

<merge
   
xmlns:android="http://schemas.android.com/apk/res/android"
   
xmlns:okCancelBar="http://schemas.android.com/apk/res/com.example.android.merge">

   
<ImageView  
       
android:layout_width="fill_parent"
       
android:layout_height="fill_parent"
   
       
android:scaleType="center"
       
android:src="@drawable/golden_gate" />
   
   
<com.example.android.merge.OkCancelBar
       
android:layout_width="fill_parent"
       
android:layout_height="wrap_content"
       
android:layout_gravity="bottom"

       
android:paddingTop="8dip"
       
android:gravity="center_horizontal"
       
       
android:background="#AA000000"
       
       
okCancelBar:okLabel="Save"
       
okCancelBar:cancelLabel="Don't save" />

</merge>

새로운 Layout 은 다음과 같은 결과를 만들어 낸다.

 새로운 CustomView  인 OKCancelBar 의 소스 코드는 매우 단순하다. 왜냐하면 두 개의 버튼을 생성하기 위한 코드는 외부 XML 파일에 별도로 지정되어 있기 때문이다. 다음 코드에서 확인 할 수 있듯이, R.layout.okcancelbar 에 지정된 Layout 이 LayoutInflate 를 통해 형상화 된 후, OKCancelBar 의 자식 View로 추가된다. 

public class OkCancelBar extends LinearLayout {
   
public OkCancelBar(Context context, AttributeSet attrs) {
       
super(context, attrs);
        setOrientation
(HORIZONTAL);
        setGravity
(Gravity.CENTER);
        setWeightSum
(1.0f);
       
       
LayoutInflater.from(context).inflate(R.layout.okcancelbar, this, true);
       
       
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.OkCancelBar, 0, 0);
       
       
String text = array.getString(R.styleable.OkCancelBar_okLabel);
       
if (text == null) text = "Ok";
       
((Button) findViewById(R.id.okcancelbar_ok)).setText(text);
       
        text
= array.getString(R.styleable.OkCancelBar_cancelLabel);
       
if (text == null) text = "Cancel";
       
((Button) findViewById(R.id.okcancelbar_cancel)).setText(text);
       
        array
.recycle();
   
}
}

 두 개의 버튼은 다음의 XML Layout에 정의되어 있다. 추가적인 Layout 없이, 부모 View 인 OKCancelBar 에 두 개의 버튼을 직접 추가하기 위해서 <merge /> 태그를 사용했다. 또한, 개별 버튼은 유지하고 관리하기 쉽게 하기 위해,  외부 XML 파일에 별도로 구현된 후, <include/> 태그를 이용해 두 번 포함되었고, 단순히 id 값만을 Override 하였다. 

<merge xmlns:android="http://schemas.android.com/apk/res/android">
   
<include
       
layout="@layout/okcancelbar_button"
       
android:id="@+id/okcancelbar_ok" />
       
   
<include
       
layout="@layout/okcancelbar_button"
       
android:id="@+id/okcancelbar_cancel" />
</merge>

  결과적으로 우리는, 효율적인 View 계층 구조를 갖으면서도, 유연하고 유지보수 하기 쉬운 Custom View 를 작성 하였다. 

 살펴본 봐와 같이, <merge/> 태그는 코드를 작성할 때, 굉장히 유용하며 깜짝 놀랄만한 일들을 해 준다. 하지만, 몇 가지 한계점도 갖고 있다. 

  • <merge/> 는 Root 태그로만 사용될 수 있다.
  • <merge/> 로 시작되는 Layout 을 형상화 할 때, 개발자는 반드시, ViewGroup 을 지정해 주어야 하고, attachToRoot 값을 true 로 설정 해 주어야 한다. (보다 자세한 설명은 inflate() 메서드에 관한 다음 내용을 살펴 보라.)

Posted by 삼스