30분넘게 구글링해서 알아낸 소중한 주소..

하지만 어떻게 다 다운받냐..많네..==;

http://www.netmite.com/android/mydroid/2.0/packages/apps/


[출처] : 

http://blackzaket.blog.me/80113159648


Android에서는 기본적으로 AlarmManager를 제공하는데..

특정 작업을 Manager에 등록해 두면 알아서 등록한 intent를 깨우준다.

 

AlarmManager am = (AlarmManager)con.getSystemService(Context.ALARM_SERVICE);

 

//RealTime으로 예약하는 경우

//반복의 경우

am.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), 1000 * 60 * 60 * 24, pi);

//한번의 경우

am.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pi);

 

//부팅경과 시간으로 계산하는 경우

ELAPSED_WAKEUP

 

 

주의 사항

간단하게 2가지의 주의 사항이 있는데

1. AlarmManager에 등록되어진 Alarm의 경우 단말이 재부팅을 하게 되면 깨끗하게 최기화 되어진다.

2. calendar 또는 date로 시간을 Set할때 현재 시간에 기준하여 알람 시간이 내일인지 오늘인지를 잘 계산해야 한다. (뭔지 모르고 엄청난 삽질을;;;;)

 

1번의 해결 방법으로는 Intent-filter를 이용하는 방법이 있는데...

알람을 초기화 하는 Activity에 다음과 같은 intent-filter를 달아주는것이다.

 

<action android:name="android.intent.action.BOOT_COMPLETED" />

 

intent에서는 getAction과 같은 값으로 Intent를 구분할 수 있다.

출처 : http://www.cyworld.com/kenur/3732812

SoundPool(int maxStreams, int streamType, int srcQuality)

 

첫번째 = 동시에 재생가능한 최대 스트림수

두번째 = 오디오 스트림 STREAM_MUSIC 고정

세번째 = 샘플링 품질

 

객체를 생성후에  사운드를 아래와같이로드함

 

int load(Context context, int resld, int priority)

int load(String path, int priority)

 

리로스나 파일로부터 사운드를 로드 한다

 

 

재생메소드

int play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)

 

첫번째 = 재생할사운드

두번째 = 좌 볼륨

세번째 = 우 볼륨

네번째 = 재생우선순위

다섯 = 반복설정

여섯 = 재생속도

 


 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
>
<Button
	android:id="@+id/play1"
	android:layout_width="fill_parent"
	android:layout_height="wrap_content"
	android:text="보통재생"
/>
<Button
	android:id="@+id/play2"
	android:layout_width="fill_parent"
	android:layout_height="wrap_content"
	android:text="볼륨 절반 재생"
/>
<Button
	android:id="@+id/play3"
	android:layout_width="fill_parent"
	android:layout_height="wrap_content"
	android:text="3회 재생"
/>			
<Button
	android:id="@+id/play4"
	android:layout_width="fill_parent"
	android:layout_height="wrap_content"
	android:text="0.5배속 재생"
/>
<Button
	android:id="@+id/play5"
	android:layout_width="fill_parent"
	android:layout_height="wrap_content"
	android:text="2배속 재생"
/>
<Button
	android:id="@+id/play6"
	android:layout_width="fill_parent"
	android:layout_height="wrap_content"
	android:text="3배속 재생"
/>			
</LinearLayout>


 

package test.Layout;

import android.app.*;
import android.content.*;
import android.media.*;
import android.os.*;
import android.view.*;
import android.widget.*;

public class Layout extends Activity 
{
	SoundPool pool;
	int ddok;
	
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        pool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
		ddok = pool.load(this, R.raw.ddok, 1);
        
        findViewById(R.id.play1).setOnClickListener(mClickListener);
        findViewById(R.id.play2).setOnClickListener(mClickListener);
        findViewById(R.id.play3).setOnClickListener(mClickListener);
        findViewById(R.id.play4).setOnClickListener(mClickListener);
        findViewById(R.id.play5).setOnClickListener(mClickListener);
        findViewById(R.id.play6).setOnClickListener(mClickListener);
    }
    
    Button.OnClickListener mClickListener = new Button.OnClickListener()
    {
    	public void onClick(View v)
    	{
    		MediaPlayer player;
			switch (v.getId()) 
			{
			case R.id.play1:
				pool.play(ddok, 1, 1, 0, 0, 1);
				break;
			case R.id.play2:
				pool.play(ddok, 0.5f, 0.5f, 0, 0, 1);
				break;
			case R.id.play3:
				pool.play(ddok, 1, 1, 0, 2, 1);
				break;
			case R.id.play4:
				pool.play(ddok, 1, 1, 0, 0, 0.5f);
				break;
			case R.id.play5:
				pool.play(ddok, 1, 1, 0, 0, 2);
				break;
				
			case R.id.play6:
				pool.play(ddok, 1, 1, 0, 0, 3);
				break;
			}

    	}
    };
}
pdf 


1.proyo
2010년 5월 21일 안드로이드 SDK 2.2 업데이트가 발표되었다. 본문은 2.1 버전을 기준으로
작성되어 있으므로 부록을 통해 간단하게나마 새로 발표된 SDK의 개발환경 설정 방법과 추
가 기능에 대해 소개한다. 아직 상세한 문서를 구하기 어렵고 DevGuide와 공식 레퍼런스만
을 참고하여 분석한 것이지만 새SDK의 기능을 둘러 보기엔 부족하지 않을 것이다.
안드로이드 2.2의 코드명은 Frozen Yoghurt의 약자인 Froyo로 붙여졌
다. 번역하자면 얼린 요구르트라는 뜻인데 안드로이드는 빵이나 디저트
류로 코드명을 붙이며 Donut, Cupcake, Eclair 식으로 첫자 알파
벳이 1씩 증가한다. Froyo의 다음 버전은 마늘빵(Ginger Bread)
로 이미 명명되어 있으며 2010년 하반기에 발표될 예정이다.
Froyo는 이전 버전인 Eclair에 비해 버전이 0.1만큼 증가한 마이
너 업그레이드 버전이지만 이전의 업데이트에 비해서는 굵직한 기능 개선이 많이 이루어졌다.
그동안 아쉽다고 여겨졌던 문제들이 대부분 해결되어 뭔가 중무장을 하고 나타난 묵직한 느낌
이 든다. 2.2에서 추가된 주요 기능들의 목록은 다음과 같으며 개별 기능들에 대해서는 잠시
후 천천히 연구해 보기로 하자.
• 전반적인 성능 향상
• 외부 저장 장치에 앱 설치 지원
• 데이터 백업 지원
• 익스체인지 지원 강화
• 플래시 10.1 지원
• OpenGL ES 2.0 지원
• 최대 8대까지 연결할 수 있는 포터블 핫스팟 기능 제공
• 음성 인식 기능 제공
이 외에 홈 스크린의 UI가 대폭적으로 변경되었으며 카메라, 캠코더의 기능들도 확장되었다.
새로운 기능을 지원하기 위한 클래스들이 추가되었고 기존 클래스의 메서드들도 추가 또는 변
경되었다. 코드와 관련된 부분은 잠시 후 예제를 작성해 보면서 상세하게 연구해 볼 것이다.
2.2의 개선 사항중 가장 먼저 눈에 띄는 부분은 성능 향상이다. 모바일 장비들은 아직까지 PC
Appendix 안드로이드 2.2 851
에 비해서는 CPU의 속도가 느리므로 운영체제의 효율성이 중요한 관건인데 Froyo에서 이
부분이 많이 개선되었다. 구체적인 향상 정도는 다음과 같다.
① 달빅 JIT 컴파일러를 최적화하여 CPU를 과중하게 사용하는 코드의 실행 속도가 2~5배 정도 빨라졌다.
② 개선된 자바 스크립트 엔진인 V8 엔진을 채용하여 자바 스크립트를 많이 사용하는 웹 페이지의 로딩
및 실행 속도가 4배 정도 향상되었다.
③ 메모리 관리 능력이 20배 정도 개선되어 작업 전환이 부드러워졌다. 제한된 메모리를 가진 장비에서
메모리 회수 속도가 개선되면 전반적인 성능이 향상된다.
물론 이는 어디까지나 제작사의 주장이므로 액면 그대로 다 믿을 필요는 없다. 특정한 부분에
대한 속도가 개선되었다는 것이지 전체적인 속도를 얘기하는 것은 아니므로 최종 사용자가 느
끼는 체감 속도 향상은 이보다 덜할 것이다. 아직 실장비가 없어 확인할 수 없지만 해외 사이트
의 테스트 결과를 보면 이전 버전에 비해 눈에 띄게 성능이 향상되었다고 한다.
기능 개선은 많이 이루어졌지만 개발 문서에 대한 지원은 별다른 개선이 없다는 점이 못내 아
쉽다. 레퍼런스에는 아직도 비어 있는 항목들이 많으며 오타도 제대로 수정되지 않았는데 아직
그럴 여유는 없는 모양이다. 운영체제의 기능만큼이나 개발자들에게 제대로 된 정보를 제공하
는 것도 중요하다. 이후 구글에서 공식 문서에 좀 더 투자해 주기를 바란다.
2
새 버전에 추가된 기능들을 구경해 보려면 개발 환경부터 업데이드해야 한다. 부록은 2.1이 설
치되어 있다는 가정하에 작성된 것이므로 업그레이드 위주로 설치 과정을 설명한다. 새로 설치
하는 경우는 당연히 업데이트와는 다르므로 본문의 1장을 따라 하되 다운로드하는 파일과 설
치 과정에서 선택하는 항목만 달라질 뿐 설치 방법은 사실상 동일하다.
주 개발툴인 이클립스는 SR2로 소폭 업그레이드되었지만 버전은 여전히 3.5이므로 굳이 업
그레이드하지 않아도 상관없다. JDK 6도Update18에서 20으로 개정되었지만 안드로이드는
JDK의 일부 기능만 사용하므로 역시 업데이트할 필요는 없다. 물론 둘 다 최신 버전으로 업데
이트해도 무방하며 새 컴퓨터에 설치할 때는 당연히 최신 버전을 설치하는 것이 좋다. SDK 업
데이트 과정은 자동화되어 있어 별도로 파일을 다운로드받거나 할 필요없이 이클립스 내에서
모든 작업이 가능하다. 아마 이후 발표되는 SDK도 동일한 방법으로 업그레이드가 가능할 것
852 안드로이드 프로그래밍 정복
이다. 여러 단계를 거쳐야 하고 순서를 잘 지켜야 하므로 다소 번거롭기는 하지만 통합 환경 안
에서 모든 업그레이드를 수행할 수 있으므로 많이 편리해졌다. 여러 번 테스트해 본 결과 다음
순서대로 업데이트하는 것이 가장 간편하다.
SDK를 업데이트하기 전에ADT를 업데이트해야 한다. 기존의 2.1 버전에서는 0.9.5가 사용
되었는데 2.2와 함께 0.9.7로 업그레이드되었으며 일부 기능이 개선되었다. 이클립스 메뉴에
서 Help/Install New software 명령을 선택한 후 Work with 목록에서 https://dlssl.
google.com/android/eclipse/ 를 선택한다. 이전에 설치를 한 적이 있다면 이 주소가 목
록에 기억되어 있으므로 콤보 박스에서 선택만 하면 된다. ADT와 DDMS의 0.9.7 새 버전
이 목록에 나타나는데 둘 다 선택한 후 설치한다.
[Next] 버튼을 누르면 설치가 시작된다. 설치중에 서명이 없다는 경고가 나타날 수도 있는데
믿을 수 있는 툴이므로 경고를 무시해도 상관없다. 네트워크를 통해 필요한 파일을 다운로드받
는데 만약 방화벽이 다운로드를 막으면 풀어 주어야 한다. 잠시 기다리면 ADT 설치가 완료되
며 새로운ADT로 교체하기 위해 이클립스 재시작해야 한다.
다음은SDK를 업그레이드한다. 이클립스의Windows/Android SDK and AVD Manager
메뉴를 선택하고 왼쪽 목록에서 Available Packages를 선택한다. 설치 가능한 소프트웨어 목
록이 나타나는데 시스템에 어떤 버전이 설치되어 있는지에 따라 실제 목록은 달라진다. 2.1까
지만 설치된 경우의 목록은 다음과 같다.
Appendix 안드로이드 2.2 853
이 목록에서 설치하고자 하는 항목을 선택한다. SDK Tools 버전 6과 SDK API 8, 문서, 샘
플 예제, Google API 8 등은 필수적으로 선택해야 한다. 제일 아래쪽의 Usb Driver
package도 선택하도록 하자. 이전 버전 개정판은 프로젝트에 꼭 필요하지 않는 한 굳이 설치
할 필요없다. 설치할 항목을 선택한 후 [Install Selected] 버튼을 눌러 다음 단계로 넘어간다.
설치 대상 항목과 라이센스 동의문이 나타나는데 Accept All을 선택하고 [Install] 버튼을 누
르면 설치가 시작된다. 대용량의 자료를 다운로드 받아야 하므로 다소 시간이 걸린다. 중간에
ADB를 재시작하겠다는 메시지가 나타나면 확인 버튼을 눌러 ADB를 재시작하되 이클립스
를 다시 시작할 필요는 없다.
여기까지 진행하면 개발을 위한 SDK는 모두 설치한 것이다. 다음은 새 SDK로 작성한 예제
를 실행할 AVD를 생성한다. 아직 실장비가 없으므로 에뮬레이터를 통해서만 새 버전의 앱을
854 안드로이드 프로그래밍 정복
실행해 볼 수 있다. Virtual Devices 페이지에서 [New] 버튼을 눌러 에뮬레이터를 새로 생성한
다. scv8이라는 이름을 주고 타겟은 Level 8의 Android 2.2 또는 Google APIs를 선택한다. SD 카
드는 32메가 정도면 충분하다. 아래쪽의 [Create AVD] 버튼을 누르면 AVD가 생성된다.
제대로 생성되었는지 목록에서 [Start] 버튼을 눌러 시험 기동시켜 보자. 잠시 기다리면 다음과
같은 에뮬레이터가 실행될 것이다. 이제 2.2 버전의SDK로 예제를 작성해서 이 에뮬레이터로
보내면 실행해 볼 수 있다.
Appendix 안드로이드 2.2 855
에뮬레이터 스킨은 바뀌지 않았지만 버전이 올라간만큼 홈 스크린이 많이 바뀌었다. 제일 먼저
눈에 띄는 것은 중앙에 있는 팁 위젯인데 초록색 로봇이 초보 사용자에게 간단한 사용법을 알
려주는 역할을 한다. 최초 6개의 도움말이 나타나며 터치하면 다음 도움말을 볼 수 있다. 보기
싫으면 로봇을 드래그해서 쓰레기통에 던져 버린다.
하단에는 전화, 론처, 브라우저가 고정된 쇼트컷에 배치되어 있다. 홈 스크린은 모두 5개의 페
이지로 구성되어 있고 드래그해서 좌우로 이동 가능하지만 고정된 쇼트컷은 스크롤되지 않고
어느 페이지에서나 보이므로 빠르게 선택할 수 있다는 점에서 편리하다. 중앙의 론처 버튼을
누르면 기본 제공되는 프로그램의 목록이 나타난다.
아쉽게도 사용자 ID에 한글이 포함된 경우 에뮬레이터가 실행되지 않는 버그는 아직 수정되지
않았다. 만약 에뮬레이터가 기동되지 않는다면 본문 52페이지 내용대로 AVD의 경로를 한글
이 없는 경로로 옮겨야 한다.
3
SDK가 제대로 설치되었는지, 새 SDK의 프로젝
트는 어떤 모습인지 테스트 프로젝트를 만들어
보자. 프로젝트를 생성하는 방법은 이전 버전과
동일하다. 메뉴에서 File/New/Android Project 항목
을 선택하고 생성할 프로젝트의 정보를 입력한다.
856 안드로이드 프로그래밍 정복
프로젝트의 이름은 FroyoTest로 주고 타겟은 Android 2.2로 설정한다. 제일 아래쪽의 Min
SDK Version은 8로 설정하거나 아니면 비워 두어도 상관없다. Properties란에 앱 이름, 패
키지 명, 액티비티명을 적당히 입력한 후 [Finish] 버튼을 누르면 프로젝트가 생성되며 패키지
탐색기에 프로젝트가 나타날 것이다.
프로젝트의 폴더 구조나 생성되는 파일의 목록은 이전 버전과 완전히 동일하다. 레이아웃 파일은
다음과 같다. 부모의 크기를 다 사용하라는 의미의 fill_parent가 2.2 버전에서 match_parent로 변경
되었지만 마법사는 여전히 fill_parent를 사용한다. 두 플래그 모두 당분간은 계속 사용할 수 있을
것으로 보인다.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
</LinearLayout>
리니어안에 텍스트 뷰가 배치되어 있으며 문자열 리소스가 지정되어 있다. 소스 코드는 이전
Appendix 안드로이드 2.2 857
버전에 비해 전혀 바뀌지 않았다. onCreate에서 레이아웃을 전개하여 액티비티에 가득 채우
기만 한다.
package exam.FroyoTest;
import android.app.Activity;
import android.os.Bundle;
public class FroyoTest extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
예제를 실행해 보자. Run/Run 항목을 선택하거나 + 을 누르면 다음 대화상자가 나
타난다. 아무 항목도 선택되어 있지 않으므로 아래, 위 키를 누른 후 를 눌러야 하는데
이후부터는 + 을 누르면 바로 실행된다.
에뮬레이터가 실행되며 빈 화면에 문자열만 나타날 것이다. 수정도 잘 되는지 코드를 약간 바
꿔 보자. main.xml의 텍스트 뷰를 다음과 같이 수정한다. 새로 추가된 match_parent 레이아
웃 값을 적용해 보고 문자열을 변경해 보았다.
ProyoTest.java
858 안드로이드 프로그래밍 정복
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="My First Android 2.2 project"
/>
수정한 파일을 저장하고 다시 실행하면 수정한 문자열이 화면에 나타날 것이다. 수정 후 재실
행할 때 xml 파일에서 + 을 누르거나 툴바의 [Run] 버튼을 누르면 xml 자체를 실행
하라는 것으로 해석하여 main.out.xml 파일을 만들고 에러를 내므로 반드시 java 파일로 포
커스를 옮긴 후 실행해야 한다.
다행스럽게도 프로젝트를 생성하고 관리하는 방법상의 변화는 거의 없다. + 을 누를
때 별도의 질문없이 바로 실행한다는 점에서 약간의 기능 개선이 있는 정도이다. 기존 방법대
로 프로젝트를 만들고 실습을 진행하면 된다.
다음은 기존 예제가 새 버전의 에뮬레이터에서 잘 실행되는지 호환성 테스트를 해 보자. 이 책
의 통합 예제인 AndroidExamAll.zip을 Import하면 아무 문제없이 컴파일되고 실행된다.
레벨 8의 에뮬레이터에서 레벨 7의 예제가 정상 실행되는 것은 당연하다고 할 수 있다. 어쨌거
나 하위 호환성이 잘 유지된다는 면에서 무척 다행스러운 일이다.
Appendix 안드로이드 2.2 859
이전 버전의 프로젝트를 계속 관리해야 한다면 새 SDK를 설치해 놓은 상태에서도 이전 버전
으로 계속 개발할 수 있다. 새 기능이 꼭 필요치 않다거나 타겟 장비의 버전이 낮다면 프로젝트
의 버전을 굳이 8로 올리지 않아도 무방하다.
4
모바일 장비의 메모리는 폰에 내장되어 있는 내부 메모리와 SD 카드처럼 별도로 확장 가능한
외부 메모리로 구분된다. 내부 메모리는 항상 장착되어 있어 언제나 사용 가능하다는 이점이
있지만 가격이 비싸므로 용량이 비교적 작다. 외부 메모리는 용량이 크고 가격이 저렴한데 현
재 16G 정도가 겨우 4만원 수준이다. 그러나 탈착 가능하므로 항상 사용 가능한 상태가 아니
라는 단점이 있다.
안드로이드의 앱은 내부 메모리에만 설치할 수 있으며 외부 메모리에는 설치할 수 없도록 되어
있는데 이런 제한이 합당한 여러 가지 이유가 있다. 설치된 프로그램이 언제나 사용 가능해야
하지만 교체 가능한 외부 메모리에 설치할 경우 외부 메모리 분리시 시스템의 설정이 깨진다는
위험이 있다. 또 교체 가능한 메모리에 설치를 허락하면 마켓에서 내려받은 유료 프로그램의 불
법 복제가 난무하는 문제도 간과할 수 없다.
합당하기는 하지만 이 제한에 의해 장비에 애초에 내장된 메모리 용량을 초과하는 앱은 설치할
수 없다는 심각한 문제가 있으며 실제로 안드로이드 폰 출시 후 상당수의 사용자들이 이 문제로
인해 불편을 호소했다. 이런 불편함이 2.2 버전에서 드디어 해결되었다. 2.2 버전부터는 외부
메모리에도 앱을 설치할 수 있다. 이를 위해 안드로이드는 매니페스트에 설치 가능한 위치를 지
정하는 installLocation 속성을 추가했다.
속성 설명
internalOnly 내부 메모리에만 설치할 수 있으며 외부 메모리에는 설치할 수 없다. 내부 메모리가 부족하면 설
치는 실패한다. 이 값이 디폴트다. 별 지정이 없거나 2.2 이전의 앱은 이 속성을 지정한 것으로 간
주된다.
preferExternal 가급적이면 외부 메모리에 설치한다. 만약 외부 메모리가 부족하거나 존재하지 않으면 내부 메모
리에 설치된다. 사용자는 설치 후에도 위치를 변경할 수 있다.
860 안드로이드 프로그래밍 정복
auto 시스템이 몇 가지 조건에 따라 설치 위치를 결정한다. 디폴트로 내부 메모리에 설치하지만 내부
메모리가 부족하면 외부 메모리에 설치한다. 사용자는 설치 후에도 위치를 변경할 수 있다.
외부 메모리에 설치하더라도 속도상의 불이익은 거의 없으며 사용자 데이터나 DB 파일 등은
여전히 내부 메모리에 저장된다. 외부 메모리에 설치되는 프로그램은 암호화되어 저장되므로
최초 설치한 장비에서만 실행된다. 외부 메모리를 다른 장비로 옮기거나 파일을 복사해서는 실
행되지 않으므로 불법 복사는 할 수 없다. 만약 실행중에 외부 메모리가 제거되면 외부 메모리
에 설치된 앱은 강제로 종료된다.
외부 메모리 지원에 의한 부작용을 최소화하기 위한 여러 가지 안전 장치가 같이 마련되었음을
알 수 있다. 외부 메모리에 설치 가능한 테스트 프로그램을 작성해 보자. 마법사로 다음 프로젝
트를 생성한다.
public class ExternalInstall extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_DOWN) {
Toast.makeText(this,"Touch Event Received",
Toast.LENGTH_SHORT).show();
return true;
}
return false;
}
}
아무 동작이 없으면 심심하므로 터치 입력을 받았을 때 토스트만 살짝 띄워 보았다. 외부 설치
를 허가하기 위해 매니페스트에 다음 속성을 설정한다.
ExternalInstall.java
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:installLocation="preferExternal"
package="exam.ExternalInstall"
android:versionCode="1"
android:versionName="1.0">
preferExternal 속성으로 지정하여 가급적 외부 메모리에 설치하도록 했다. 실행하면 에뮬레
이터에 설치되고 빈 화면이 나타나며 터치 입력도 잘 받는다. 어디에 설치되어 있는지
Settings/Applications/Manage applications 메뉴를 찾아 들어가 ExternalInstall 예제를
선택해 보자. 다음 관리창이 뜰 것이다.
오른쪽 가운데에Move to phone 버튼이 배치되어 있다. 이 버튼은 프로그램 설치 위치를 내
부 메모리로 이동하라는 명령이며 현재는 외부 메모리에 설치되어 있음을 알 수 있다. 과연 그
런지DDMS의 파일 탐색기로 확인해 볼 수 있다.
이 버튼을 누르면 내부 메모리로 옮겨지며 버튼의 캡션은 Moving으로 바뀌었다가 잠시 후
Move to SD card로 변경된다. 즉 이 버튼은 현재 설치된 위치를 토글하는 기능을 제공하는
Appendix 안드로이드 2.2 861
862 안드로이드 프로그래밍 정복
데 필요에 따라 위치를 자유롭게 옮길 수 있다. 별다른 위치를 지정하지 않은 FroyoTest 예제
의 경우는 어떤지 보자.
Move to SD card 버튼이 사용 금지되어 있다. 즉, 현재는 내부 메모리에 설치되어 있는데 외
부 메모리로 옮길 수는 없다는 뜻이다. 2.1 버전 이전의 모든 예제들은 installLocation 속성이
지원되지 않으므로 무조건 내부 메모리에만 설치할 수 있다. 이전 버전의 프로그램도 외부 메
모리에 설치 가능하게 하려면 매니페스트를 수정한 후 다시 컴파일해야 한다. 최소 SDK 설정
(minSdkVersion)은 그대로 두고 타겟 레벨만 8로 변경한 후 이 속성만 지정하는 것도 가능하
다. 타겟을 8로 변경하더라도 최소 SDK 버전은 그대로 유지되므로 이전 버전의 장비에서도
문제없이 설치 및 실행된다.
외부 설치가 가능하려면 타겟 장비는 반드시 2.2 버전이어야 한다. 이전 버전에는 외부 설치
기능이 없으므로 속성을 지정해도 인식하지 않으며 에러는 나지 않지만 무시당한다. 응용 프로
그램은 2.2 버전으로 컴파일했더라도 설치되는 장비가 2.1이면 외부 설치는 불가능하다. 사실
2.2 앱을 2.1 장비에 설치하는 것 자체가 불가능하다.
외부 설치 기능은 값비싼 메모리를 절약한다는 면에서 유용하지만 반드시 내부에만 설치해야
하는 앱도 있다. 서비스나 앱 위젯, 알람 등의 앱은 설치 후 항상 사용 가능해야 하므로 반드시
내부에 설치해야 하며 SD 카드를 제거하더라도 시스템의 설정이 깨지지 말아야 한다. 예를 들
어 MP3 플레이어는 재생을 위해 서비스를 등록하는데 이 서비스는 언제든지 사용 가능해야
Appendix 안드로이드 2.2 863
한다. 외부에 설치된 서비스는 SD 카드 제거시 강제 종료되며 SD 카드를 다시 마운트해도 자
동으로 재시작되지 않는다.
앱 위젯의 경우는 더 심각해서 SD 카드 제거시 부팅을 다시 해야만 재실행된다. SD 카드가 있
을 때만 재생 가능해서는 안되므로 이런 앱은 반드시 내부에 설치해야 한다. 서비스 류는 응용
프로그램이라기보다는 운영체제의 확장이므로 외부 메모리 상황과 상관없이 항상 사용 가능해
야 한다. 이점은 사실 PC도 마찬가지인데 USB 외장 하드에 오피스나 개발툴 따위를 설치하
는 사람은 없다. D 드라이브에 프로그램을 설치해 놓았을 때 D 드라이브를 떼 버리면 컴퓨터
는 완전 바보가 된다.
반면 외부에 설치해도 별 문제가 없는 프로그램도 많다. 다른 프로그램과 엮이지 않는 독립적
인 프로그램은 외부에 설치해도 무방하다. 그래픽 이미지를 많이 사용하는 게임이나 대용량의
데이터를 필요로 하는 사전류들은 가급적이면 외부에 설치하는 것이 유리할 것이다. 외부 설치
된 앱은 SD 카드를 제거하면 당연히 실행할 수 없으며 SD카드가 마운트되어 있을 때만 실행
가능하다. 게임은 강제 종료되어도 언제든지 재실행할 수 있으므로 문제될 것이 없다. 두 개의
SD 카드에 각각 다른 프로그램들을 설치해 놓고 번갈아 가며 쓸 수도 있다.
이 기능은 사실 꼭 필요해서 만들었다기보다는 비난을 받기 싫어서 넣은 기능이다. 구글도 이
문제를 몰랐을 리가 없으며 사실 앱은 내부 메모리에만 설치하는 것이 논리적으로 옳다. 그러
나 두 메모리의 용량, 가격차가 너무 현격하기 때문에 초기 실장비를 제작하는 업체가 내부 메
모리를 충분히 제공하지 않았고 그러다 보니 안드로이드의 큰 약점으로 부각된 것이다. 다른
운영체제와 비교를 당하는 것이 구글 입장에서 결코 유쾌하지 않았을 것이며 또 저가의 장비도
지원해야 한다는 명분으로 인해 이 기능이 늦게나마 포함된 것이다.
메모리 값이 더 저렴해질 것이라는 것은 누구나 다 아는 사실이고 앞으로 발표되는 장비는 충
분한 내부 메모리를 제공할 것이다. 최근 발표된 Galaxy S는 2G의 설치 메모리와 16G의 내
장 메모리를 제공해 앱 설치에 거의 문제가 없다. SD 카드는 대용량의 동영상이나 사진, 음악
파일을 저장하는 것이 본래 용도이지 앱 설치용은 아니다. 다만 백과 사전류 같은 특수한 예외
들이 있기 때문에 SD 카드 설치도 허락하게 된 것이다.
864 안드로이드 프로그래밍 정복
5
모바일 장비도 PC와 마찬가지로 프로그램을 이것 저것 깔다 보면 점점 느려지므로 가끔은 깔
끔하게 포맷을 다시 해야 한다. 리셋하는 방법은 뻔하다. 이전 데이터 백업해 놓고 프로그램 재
설치 후 원래 데이터를 가져 오는 것이다. 장비를 교체할 때도 동일한 과정을 반복해야 하는데
이전 데이터와 세팅을 새 장비에 수작업으로 복원하는 것은 굉장히 번거로운 일이다. 장비를
분실한 경우는 원본 데이터가 없으므로 아예 복구가 불가능하다.
Froyo는 이런 경우를 위해 데이터와 세팅을 백업하고 복원하는 솔루션을 제공한다. 응용 프로
그램이 주요 데이터와 세팅을 입출력하는 에이전트를 작성해 두면 백업 관리자가 필요할 때 에
이전트를 호출하여 백업과 복구를 수행하는 식이다. 이 과정은 사용자에게는 보이지 않으며 백
그라운드에서 완전히 자동으로 수행된다. 저장된 데이터는 복구시에만 읽을 수 있으며 사용자
가 임의로 액세스할 수 없으므로 싱크와는 다른 개념이다.
데이터가 백업되는 장소는 클라우드 저장소(cloud storage)인데 구체적인 위치는 장비와 사업
자에 따라 달라진다. 대개의 경우는 원격지의 네트워크이겠지만 실제 위치는 굳이 몰라도 상관
없다. 사업자는 백업 지원을 위해 별도의 저장소를 준비해야 하는 부담이 있지만 사용자들이
이 기능에 맛을 들이면 도저히 벗어날 수 없는 충성스런 고객이 되므로 사업자 입장에서 아주
매력적인 기능이라고 할 수 있다. 저장된 정보를 다른 앱이 사용할 수는 없지만 보안이 완벽한
것은 아니므로 패스워드같은 민감한 데이터는 저장하지 말아야 한다.
백업/복원 기능을 사용할 응용 프로그램은 내부에 에이전트를 구현해 놓고 백업 관리자의 요청
이 있을 때 백업할 데이터를 전달한다. 에이전트는 백업 관리자와 통신하는 응용 프로그램 내
부의 객체이며 미리 약속된 방식으로 백업 관리자와 협조적으로 데이터를 백업 및 복원한다.
응용 프로그램은 다음 두 가지 방법으로 에이전트를 구현한다.
• BackupAgent 클래스 확장 : 백업 관리자에 의해 호출되는 onBackup, onRestore 메서드를 직접
구현하는 방식이다. 섬세한 버전 관리를 할 수 있으며 파일의 일부만 선택적으로 백업할 수 있고 데
이터베이스에 저장할 수도 있다. 자유도는 높지만 스트림을 직접 제어하므로 예외 처리가 완벽해야
하며 파일 액세스시 동기화에도 유의해야 하므로 다량의 코드가 필요하고 난이도도 높은 편이다. 고
급 백업 기능이 필요치 않으면 가급적 이 방법은 사용하지 않는 것이 좋다.
• BackupAgentHelper 클래스 확장 : 이 클래스에는 BackupAgent의 기능 일부가 미리 구현되어
있으며 onBackup, onRestore를 내부에서 알아서 처리한다. 응용 프로그램은 특정 타입의 데이터를
Appendix 안드로이드 2.2 865
입출력하는 도우미 객체를 등록해 놓기만 하면 된다. FileBackupHelper 클래스는 파일을 입출력하
고 SharedPreferencesBackupHelper 클래스는 프레프런스의 세팅을 저장 및 복원한다. 도우미에
백업 대상 파일이나 프레프런스 키의 목록을 전달하고 addHelper 메서드로 도우미를 등록해 놓기만
하면 나머지는 자동으로 수행된다.
백업 관리자가 통신할 대상을 찾을 수 있어야 하므로 에이전트의 이름을 매니페스트에 기록해
놓아야 한다. application 엘리먼트의 backupAgent 속성에 에이전트 클래스 이름만 기록해
놓으면 백업 관리자가 이 객체를 생성하여 백업과 복구에 필요한 메서드를 호출할 것이다.
restoreAnyVersion 속성은 버전이 달라도 복원할 것인가를 지정하는데 디폴트는 false이다.
버전간의 데이터 구조가 동일하거나 별도의 수동 변환 코드가 있으면 이 속성을 true로 지정
한다.
<application android:label="MyApplication"
android:backupAgent="MyBackupAgent">
응용 프로그램은 백업 대상 데이터의 일부가 변경될 때 백업 관리자의 dataChanged 메서드
를 호출하여 백업을 갱신할 것을 요청한다. 이때 백업 관리자는 에이전트와 함께 데이터를 다
시 백업할 것이다. 매 요청이 들어올 때마다 백업을 수행하지는 않으며 요청들을 모아 두었다
가 한꺼번에 백업함으로써 백업의 효율성을 높인다. bmgr 쉘 명령을 사용하면 백업 관리자에
게 즉시 백업을 명령할 수도 있는데 이 기능은 개발중의 테스트를 위해 아주 요긴하다.
복원은 시스템이 알아서 수행하므로 직접 요청할 필요는 없지만 꼭 수동 복원하려면
requestRestore 메서드를 호출할 수는 있다. 시스템은 응용 프로그램이 설치될 때 클라우드
저장소를 점검해 보고 이전에 백업해 놓은 정보가 있으면 가져와 복원한다. 사용자에게는 이
과정이 보이지 않으므로 새로 설치한 프로그램이 이전에 사용하던 모습 그대로 실행되는 마법
같은 일이 벌어진다.
866 안드로이드 프로그래밍 정복
백업 에이전트를 구현하는 방법에 대해서는 DevGuide에 상세하게 설명되어 있으며 Backup
and Restore 샘플 예제도 제공된다. 예제의 길이도 짧고 주석도 풍부하게 기술되어 있어 이
예제를 보면 구현 절차를 어렵지 않게 터득할 수 있다. 그러나 이 예제를 에뮬레이터에서 테스
트해 본 결과 백업 및 복구는 되지 않았는데 에뮬레이터는 클라우드 저장소가 없기 때문이다.
이 기능이 동작하려면 개통된 실제 장비가 필요하며 사업자가 저장소를 제공해야 한다.
모바일 장비는 분실 및 교체가 잦으므로 운영체제 차원에서 백업 기능을 제공하는 것은 멋진
일이다. 그러나 실용성에 대해서는 다소 의아스러운데 응용 프로그램마다 별도의 복잡한 코드
가 필요하고 백업시마다 네트워크 접속 비용이 발생하는 것도 문제다. 어디서나 무료 WiFi에
접속할 수 있고 배터리가 항상 넉넉하다면 이 기능이 꽤 쓸만할 것이며 특히 기업 사용자에게
아주 유용할 것이다.
6
멀티미디어 관련 클래스들이 일부 확장되었다. 먼저 사운드를 재생하는 SoundPool 클래스부
터 연구해 보자. 이 클래스는 MediaPlayer의 서비스를 사용하므로 압축 포맷을 재생해도
CPU 점유율이 높지 않다. 또한 동시에 여러 개의 사운드를 재생할 수 있어 게임 제작에 유용
하며 재생 속도나 볼륨도 개별적으로 지정할 수 있어 활용성이 높다. 다만 대용량의 사운드를
처리할 때 로드 시간이 오래 걸리는 것이 단점인데 이를 보완하기 위해 다음 메서드가 추가되
었다.
void setOnLoadCompleteListener (SoundPool.OnLoadCompleteListener listener)
이 메서드는 음원 로드가 완료될 때 호출되는 리스너를 지정함으로서 대용량의 음원이 로드
되는 즉시 재생 가능하다. 리스너를 지정해 놓으면 음원 로드가 완료될 때 다음 메서드가 호
출된다.
void onLoadComplete (SoundPool soundPool, int sampleId, int status)
인수로 전달되는 SoundPool 객체와 로드한 음원의 ID, 상태를 참조하여 음원을 즉시 재생할
수 있다. SoundPool은 동시에 여러 개의 효과음을 재생할 수 있는데 이 기능을 지원하기 위해
재생중인 모든 사운드를 한꺼번에 정지 및 재개할 수 있는 메서드가 추가되었다.
Appendix 안드로이드 2.2 867
void autoPause ()
void autoResume ()
이전에도 pause, resume 메서드가 제공되었지만 개별적인 스트림 단위로만 정지/재개를 수행
했었다. 게임처럼 여러 사운드가 동시에 출력되는 프로그램은 단 하나의 메서드 호출로 전체
사운드를 통제할 수 있으므로 아주 실용적이다. 다음 예제는 상기의 메서드들을 테스트한다.
public class LoadComplete extends Activity {
SoundPool pool;
int stream;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
pool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
findViewById(R.id.load1).setOnClickListener(mClickListener);
findViewById(R.id.load2).setOnClickListener(mClickListener);
findViewById(R.id.pause).setOnClickListener(mClickListener);
findViewById(R.id.resume).setOnClickListener(mClickListener);
}
SoundPool.OnLoadCompleteListener mListener =
new SoundPool.OnLoadCompleteListener() {
public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
if (status == 0) {
stream = soundPool.play(sampleId, 1, 1, 0, 0, 1);
}
}
};
Button.OnClickListener mClickListener = new Button.OnClickListener() {
public void onClick(View v) {
MediaPlayer player;
switch (v.getId()) {
case R.id.load1:
int song = pool.load(LoadComplete.this, R.raw.goodtime, 1);
pool.play(song, 1, 1, 0, 0, 1);
LoadComplete.java
868 안드로이드 프로그래밍 정복
break;
case R.id.load2:
pool.setOnLoadCompleteListener(mListener);
pool.load(LoadComplete.this, R.raw.goodtime, 1);
break;
case R.id.pause:
//pool.pause(stream);
pool.autoPause();
break;
case R.id.resume:
//pool.resume(stream);
pool.autoResume();
break;
}
}
};
}
레이아웃에는 버튼 4개가 배치되어 있으며 raw 폴더에는“즐거운 시간 되십시오”라는 음성
리소스가 저장되어 있다. 각 버튼들을 순서대로 눌러 보자.
첫 번째 버튼은 사운드를 로드한 후 곧바로 재생하는데 로드중에 재생하므로 제대로 출력되지
않는다. 음원 크기가 아무리 작아도 로드에는 다소간의 시간이 걸리므로 play 메서드가 호출될
때는 아직 준비가 안된 상태이기 때문이다.
두 번째 버튼은 로드 완료 리스너를 등록해 놓고 로드 명령을 내리며 리스너가 호출될 때 사운
드를 재생한다. 이렇게 하면 사운드가 준비된 상태에서 바로 재생 가능하며 로드되는 동안에도
다른 작업을 할 수 있다는 이점이 있다. 물론 이상적인 방법은 메서드 내부에서 사운드를 로드
하지 않고 생성자나 onCreate에서 미리 로드해 놓는 것이다. 이 기능은 임의의 사운드를 실행
중에 로드해서 재생할 때 유용하다.
Appendix 안드로이드 2.2 869
나머지 두 버튼은 재생을 잠시 멈추거나 재개하는데 이 예제는 재생하는 사운드가 하나밖에 없
으므로 pause, resume으로도 동일한 기능을 구현할 수 있다. 그러나 여러 개의 사운드를 재생
중이었다면 개별 사운드마다 정지를 명령해야 하므로 번거로워진다.
녹음 및 녹화 기능을 담당하는 MediaRecorder 클래스도 채널수, 샘플링 비율 등을 지정하는
메서드가 추가되었다. 고정된 품질로만 녹화를 하는 것이 아나라 품질과 용량을 적절한 수준에
서 선택할 수 있다. YouTube 등의 동영상 공유 사이트에 올릴만한 적절한 품질을 직접 선택
할 수 있다. 프로파일은 하드웨어의 능력치를 조사하는 읽기 전용의 클래스인데 응용 프로그램
이 하드웨어의 모든 능력을 십분 활용할 수 있다.
void setAudioEncodingBitRate (int bitRate)
void setAudioSamplingRate (int samplingRate)
void setVideoEncodingBitRate (int bitRate)
void setProfile (CamcorderProfile profile)
에뮬레이터는 녹화를 지원하지 않으므로 이 메서드를 테스트하려면 2.2 버전의 실장비가 필요하
다. 3D 그래픽 라이브러리인 OpenGL ES도 2.0으로 업그레이드되었다. 그외 ImageForma 클래스
가 추가되었으며YUV 포맷을 관리할 수 있는 API가 추가되었다.
7
굵직한 성능 개선 외에 자잘한 개선 및 변경 사항들도 많은데 간략하게 요약만 하고 추후에 상
세하게 연구해 보도록 하자.
fill_parent 이름 변경
레이아웃 속성값인 fill_parent 플래그의 명칭이 match_parent로 변경되었다. fill_parent는
부모의 폭이나 높이를 모두 사용하라는 뜻인데 실제로는 부모의 크기에서 안쪽 여백은 제외된
다. 즉, 위젯이 안쪽 여백을 가질 경우는 여백 때문에 부모를 가득 채우지 못하는 상황이 발생
하며 따라서 fill이라는 명칭이 직관적이지 못한 경우가 있다. 이런 의미상의 불일치를 해소하
기 위해 fill_parent에서 fill이라는 단어를 match로 변경하였다.
870 안드로이드 프로그래밍 정복
당연한 얘기겠지만 fill_parent라는 명칭도 후방 호환성을 위해 계속 사용할 수 있다. 실제 상
수 정의를 보면 fill_parent와 match_parent 모두 -1로 정의되어 있으므로 이름만 다를 뿐이
지 의미는 같다. 앞으로는 가급적 새로운 속성값을 쓰는 것이 좋겠지만 이전 버전과의 호환성
을 유지해야 한다면 당분간은 fill_parent를 고수하는 것이 더 현실적이다. 실제로 2.2의 마법
사조차도 match_parent 속성값을 사용하지 않는다.
속성의 이름이 좀 더 분명해진 것은 좋지만 기존 개발자들은 명칭 변경으로 인해 다소 혼란스러
워할 것이며 새 명칭을 사용하면 2.2 버전 이상에서만 컴파일된다는 부작용도 생겨 버렸다. 역
시 어떤 제품이나 버전이 올라가면 찌꺼기가 생길 수밖에 없는 모양이다. 의미가 조금 틀리더라
도 기존 개발자의 지식은 최대한 존중해주는 것이 오히려 더 나은 선택이 아닐까 생각된다.
최적화 금지 플래그 vmSafeMode
매니페스트의 <application> 엘리먼트에 vmSafeMode라는 속성이 추가되었다. ApplicationInfo
클래스에는 이 속성값을 나타내는 FLAG_VM_SAFE_MODE 플래그도 추가되었다. 이 속
성이 true이면 JIT 컴파일러의 최적화가 금지된다. JIT 컴파일러는 극한의 효율 향상을 위해
컴파일중에 코드의 최적화를 수행하는데 일반적으로 이 최적화에 의한 부작용은 거의 없다.
그러나 미처 예상하지 못한 민감한 문제로 인해 최적화 전과 후의 동작이 약간씩 달라지는 경
우가 드물게 발생하는데 이런 경우 최적화를 금지시켜 개발자의 의도대로 컴파일하도록 강제
해야 한다. 응용 프로그램 수준에서는 이 플래그를 사용할 경우가 극히 드물 것이다.
카메라/캠코더의 기능 개선
모바일 기기의 핵심 액세서리인 카메라의 기능이 일부 개선되었다. 기존에는 가로 모드만 지원
했으나 세로 모드도 지원하기 시작했으며 줌 지원 API도 추가되었고 노출, 각도, 초점 거리 등
의 파라미터가 생겼다. 하드웨어의 능력치를 최대한 조사하여 활용할 수 있는 프로파일 클래스
가 추가되었으며GPS에서 Exif 정보를 좀 더 상세하게 조사할 수 있다.
추가된 기능들은 물론 카메라 제작에 바로 사용할 수 있으며 에뮬레이터에 기본 설치된 카메라
에도 이 기능들이 적용되어 있다. 그러나 카메라는 하드웨어에 강하게 종속적이어서 장비 제작
사들이 이미 필요한 API를 확장해서 사용하고 있으므로 추가된 API가 당장 성능 개선으로 이
어지지는 못할 듯하다. 커스텀 카메라 제작에는 추가된 API 들이 유용하게 사용될 것이다.
Appendix 안드로이드 2.2 871
익스체인지 지원
마이크로소프트의 메일 솔루션 익스체인지에 대한 지원이 강화되었다. 알파벳과 숫자키를 같
이 사용하는 비밀번호를 채택하여 보안성을 높였고 달력과 자동 싱크를 지원한다. 분실된 장비
로 인해 민감한 정보가 유출되는 것을 방지하기 위한 원격 장비 리셋 기능도 지원하는데 이는
기업용 장비에 필수적인 기능이다. 전역 디렉토리 목록에서 수신자의 주소를 자동 완성할 수
있는 편의 기능도 지원된다.
핫 스팟 기능 지원
모바일 장비를 휴대용 WiFi 핫 스팟으로 활용할 수 있다. 이 기능을 사용하면 모바일 장비에
최대 8대까지의 장비를 물려 동시에 인터넷을 사용할 수 있다. 이런 기능을 테더링
(Ththering)이라고 하는데 폰의 인터넷 접속 기능을 빌려 다른 기기에 인터넷 연결을 제공하
는 것이다. 통신 사업자는 이런 기능을 좋아하지 않지만 현실적으로 이를 막을 방법은 없다. 물
론 속도는 기대에 미치지 못할 것으로 예상된다.
PC에서 마켓 앱 설치
이전에는 폰에서 마켓을 검색하여 필요한 앱을 설치했으나 이제는 PC에서 마켓을 검색하고
PC에서 앱을 설치하면 폰으로 다운로드된다. PC의 빠른 네트워크와 넓은 화면에서 앱을 신
속하게 검색할 수 있고 설치도 편리하다. 마켓은 장비의 구성에 맞는 앱만 필터링하여 보여 주
므로 실행 가능한 앱만 설치할 수 있다. 또한 마켓은 새 버전의 앱이 올라 왔을 때 폰에 설치된
프로그램을 자동으로 업데이트하는 기능도 지원한다.
이상으로 안드로이드 2.2에서 확장된 여러 가지 기능들을 소개했고 일부는 예제도 작성해 보
았다. 이 외에도 UI Mode 관리자나 음성 인식 등의 흥미로운 기능들이 있다. 부지런히 새 기
술을

 

자바 취업준비생입니다.

다름이 아니라 이번에 처음으로 만든 어플이 있어서 올려봅니다.

올리는 이유는 펍가족님들에게 조언 좀 얻을려구영..^^;


device.pngmain.png

 

 

용기있는 왕초보의 프로젝트입니다..이름은 허접한 그림메모 어플..^^;;;;;; ==;

어플 기능 소개

1.기본적인 리스트

2.미리보기 기능 구현

3.프리드로우 기능 구현(자바 그대로 구현)

4.옵션 메뉴 기능 구현

학습된 내용

1.커스텀 CursorAdapter 공부

2.인텐트 기본 공부

3.Bitmap 기본 변환 공부

4.기본 입출력 공부

5.기본 직렬화 IO 공부

6.DB 기본 컨트롤 공부 등등

단 2장짜리 어플이지만 상당히 공부된 걸로 판단됩니다..^^;;

최적화는 못했습니다..아직 실력이..T.T

사인안된 .apk하나랑 (unsigned랑 sign이랑 있는데 뭔지 잘 모르겠음..ㅋ)

풀소스 올려봅니다. 아주 초보분들은 조금이라도 도움될듯..고수분들에게 보이기엔 상당히 부끄럽습니다..^^;

이제 몇개월만 더 하면 과정 끝나서 안드로이드 취업해야 하는데.... 더 분발해야 할듯..

혹시 여기 올리면 안되면 바로 지우겠습니다..


출처 : http://huewu.blog.me/110084228131

안드로이드 인텐트 관련해서, 두 가지 신기한 클래스가 제공됩니다. 이름만 들어서는 그 쓸모가 무엇인지 애매한, PendingIntent 와 IntentSender 가 바로 그 주인공입니다. 개발자 사이트에 서술된 내용을 살펴보아도 두 가지가 하는일이 정확히 무엇인지, 그리고 특히, 둘 사이의 차이점이 무엇인지 좀 헷갈립니다. 두 가지 클래스는 어떤 역할을 수행하고, 어떤 차이점이 있으며, 안드로이드 어플리케이션을 개발할 때, 어떻게 유용하게 사용될 수 있는지 간단하게 정리해 보았습니다. 


1.PendingIntent

 PendingIntent 은 비교적 이해하기 쉽습니다. 커뮤니케이션에는 세 가지 기본 요소가 있습니다. 메세지, 송신자, 수신자. 인텐트는 메세지 입니다. 수신자는 해당 인텐트를 수신하기 위한 인텐트 필터를 갖고 있는 컴포넌트 입니다. 송신자는 인텐트를 보내기 위한 API (startActivity/startService 등)를 호출한 컴포넌트입니다. 

<부디 나쁜데 쓰지 말고 내가 시킨것만 잘 하시오...>

 PendingIntent 는 인텐트를 전송하고자 하는 '송신자'가 인텐트를 하나 생성한 후, 별 도의 컴포넌트에게 '이 인텐트를 나중에 나 대신 보내 주렴.' 하고 전달하고자 할 때 사용되는 클래스입니다. 즉, 내가 친구에게 은행 통장에서 돈을 대신 뽑아달라고 부탁하며, 뽑을 돈의 액수를 알려주고 (인텐트), 내 카드를 빌려주는 것과 비슷한 개념이라고 생각 할 수 있습니다. 당연히, PendingIntent 를 사용할 때는 내가 맡긴 카드가 악용되지 않도록, '권한' 문제에 관해서 신경을 기울일 필요가 있으며, 이와 동시에 안드로이드 플랫폼 상에서도 PendingIntent 의 권한 문제를 제어할 수 있는 API는 물론이고, 다양한 FLAG를 제공해 주고 있습니다. 

int FLAG_CANCEL_CURRENT 이전에 생성한 PendingIntent 는 취소하고, 새롭게 하나를 만듭니다. (친구에게 예전에 빌려준 카드를 정지 시키고 새롭게 하나 신청합니다.)
int FLAG_NO_CREATE 현재 생성된 PendingIntent 를 반환 합니다. (친구 보고 내가 빌려준 카드를 당장 가져와 보라고 요청합니다. 이 후에, 해당 카드를 회수 할 수도 있습니다.)
int FLAG_ONE_SHOT 이 플래그를 이용해 생성된 PendingIntent 는 단 한번 밖에 사용될 수 없습니다. (일회용 카드)
int FLAG_UPDATE_CURRENT 만일 이미 생성된 PendingIntent 가 존재 한다면, 해당 Intent 의 내용을 변경합니다. (친구에게 전화해서, 인출할 돈의 액수를 다시 알려줍니다.)

 따라서, 저는 의미적으로 'PendingIntent' 를 '위임된 인텐트' 라고 해석할 수 있지 않을까 생각합니다.  PendingIntent 는 여러가지로 유용하게 사용될 수 있는데(여러가지 이유로 특정 패키지만 사용가능한 컴포넌트를 다른 컴포넌트와 상호 작용시킬 필요가 있는 경우 등....)그 중에서도, 안드로이드 화면 상단에 위치한 'Notification Bar' 와 상호 작용하는 어플리케이션을 작성할 때 널리 사용됩니다.

 예를 들어, 인터넷 상에서 음악을 다운로드 받는 어플리케이션이 있습니다. 음악 다운로드가 진행 중일 때, 'Notification Bar' 에 진척 상황이 나타나도록 구현하였습니다. 그리고 다운로드가 완료된 후에 사용자가 해당 알림 정보를 클릭하면, 다운로드 완료된 음악이 재생됩니다. 얼핏 생각해보면 'NotificationBar' 가 특정 아이콘이 클릭되었을 때, 어떻게 알고 음악 재생 Activity 를 띄우는지 신기하게 여겨질 수도 있습니다. 이 과정은 우리가 'Notification Manager' 에게 특정 아이콘을 등록함과 동시에 해당 아이콘이 클릭되는 순간에 전달되어야 PendingIntent 를 넘겨 주기 때문에 가능합니다. 

public void setLatestEventInfo (Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent)


 위와 같이, Notification 클래스에는 해당 Notification 이 수행할 PendingIntent 값을 설정할 수 있도록 되어 있습니다. 즉, PendingIntent 를 잘 사용하면, 다른 컴포넌트들과 상호 작용할 때, 실재로 수행할 일 자체를 PendingIntent 를 통해 추상화 할 수 있음으로(음악을 재생해라... 라는 구체적인 명령 대신, 전달받은 PendingIntent 를 수행하라 식으로...), 보다 더 확장성있는 어플리케이션 컴포넌트를 구성할 수 있습니다.

2.IntentSender
 IntentSender 는 좀 애매합니다. 무엇보다도 IntentSender 가 사용되는 꼭 맞는 샘플 코드를 찾기가 어렵더군요. IntentSender 를 생성하는 방법도 조금 색다른데, PendingIntent 를 우선 생성한 뒤, getIntentSender() API 를 호출해야합니다. 처음에는 IntentSender 는 좀 더 추상화된 PendingIntent 가 아닐까 생각했습니다. (두 클래스 간의 직접적인 상속관계가 있는 것은 아니지만...)

 하지만, 여러가지 자료를 좀 뒤져보고 (내용은 거의 없지만...) 결정적으로 안드로이드 프레임워크 소스를 살펴본 결과 허무한 결론을 내리고 말았습니다. IntentSender 는 PendingIntent 의 쌍둥이 클래스 입니다. 말하자면, PendingIntent 에 비하면 조금 못난... 쌍둥이 동생이라고 할 수 있습니다. 위임 받은 인텐트를 전달 할 수 있다는 점에서는 PendingIntent 와 정확하게 동일하지만, PendingIntent 와 비교하면 사용하기가 좀 어렵고 결정적으로 '권한' 을 관리하기 위한 API 를 제공해 주지도 않습니다. 
<멸종 위기 클래스... IntentSender. 다음 버전에는 없어지지 않을까요?>

 이 녀석 도데체 어디에 쓰이는 걸까요? 결론은 쓰이는 곳이 없다;;; 입니다. 안드로이드 풀소스를 뒤져봐도 사용되는 곳은 전무합니다. 심지어 IntentSenderTest 에서도 사용되는 것은 PendingIntent 뿐입니다. 혹시라도 저처럼 IntentSender 가 뭐에 쓰이는 물건인고... 하고 궁금해 하시는 분이 있다면, 아 그 못난 PendingIntent 쌍둥이 동생? 정도로 가볍게 넘어가셔도 좋을 듯 하네요.
단계별 구현방법

1.NotificationManager 인스턴스를 확보한다.
2.Notification 인스턴스를 생성하고 아이콘,알림메시지 문자열,알림메시지 발생시각등을 지정한다.
3.PandingIntent 인스턴스를 생성하고 알림 메시지를 클릭했을때 실행할 엑티비티(NotifyMessage)를 대상으로 지정한다.
4.setLatesEventInfo()메소드를 사용해 알림메시지를 클릭하면 알림메세지에 대한 제목과 내용을 표시하고,사용자가 알림메시지를 클릭하면 인텐트를 던져 엑티비티를 실행한다.
5.알림메세지 개수값을 하나 증가시킨다.
6.NotificationManager에 Notification인스턴스를 넘겨 알림메세지를 표시한다.


package org.exam;

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

import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class HandlerExampleAct extends Activity {
    /** Called when the activity is first created. */
 private static final int NOTIFY_ME_ID = 1337;
 private Timer timer = new Timer();
 private int count = 0;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
        Button btn = (Button)findViewById(R.id.btn1);
       
        btn.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    // TODO Auto-generated method stub
    TimerTask task = new TimerTask(){
     @Override
     public void run(){
      notifyMe();
     }     
    };
    
    timer.schedule(task, 5000);
   }
  });
       
        btn = (Button)findViewById(R.id.btn2);
       
        btn.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    // TODO Auto-generated method stub
    NotificationManager mgr =
     (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
    
    mgr.cancel(NOTIFY_ME_ID);
   }
  });
    }
   
    private void notifyMe() {
  // TODO Auto-generated method stub
  final NotificationManager mgr = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
  Notification note =new Notification(R.drawable.icon,"알림메세지",System.currentTimeMillis());
  PendingIntent i =PendingIntent.getActivity(this, 0, new Intent(this,NotifyMessage.class), 0);
  
  note.setLatestEventInfo(this, "알림제목", "알림메세지 본문입니다", i);
  
  note.number = ++count;
  
  mgr.notify(NOTIFY_ME_ID,note);
}
=============================================================================================
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class NotifyMessage extends Activity {
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  TextView txt=new TextView(this);
  
  txt.setText("알림 메시지!");
  setContentView(txt);
 }
}

좀 많이 괜찮은 사이트 굿굿!!!
http://foxmann.blog.me

출처 : http://blog.naver.com/baljern?Redirect=Log&logNo=140112776760

안드로이드 에뮬레이터가 필요해 몇 가지를 다운로드 받았지만 마켓을 이용하려면 1.5나 1.6버전을 사용해야 했기 때문에, 2.1용을 찾아 헤매다 구하지 못하고 Android 2.2 (Froyo)의 시스템 이미지 파일을 구했다.  개인적인 용도로 만든 포스트지만, 비슷한 경로로 에뮬레이터에서 마켓을 이용하는 방법을 찾는 사람들을 위해 링크를 남겨둔다.

system.img (84.19MB) Download  


먼저 위의 파일을 에뮬레이터에서 이용하기 위한 구성은 Android SDK r06 + API 8 그리고 Andriod SDK는 JDK 등이 필요하다.  혹시라도 안드로이드 앱을 제작하기 위한 기본환경 구축이 되어있지 않다면, 아래의 링크를 참조하여 그냥 순서대로 모두 설치한다.
   

안드로이드 어플리케이션 개발환경 구축 http://blog.naver.com/baljern/140112647750

 

 

 

먼저 '사용자의 경로:\android-sdk-windows\platforms\andriod-8\images' 폴더에 있는 system.img파일의 이름을

system.bak로 바꿔주고, 위에서 다운로드 받은 system.img 파일을 복사해 넣는다.

 

 

 

SDK Setup.exe를 실행한다.

 

 

 

Refresh Sources가 나오면 Close..

 

 

 

Packages to Install은 Cancle을 클릭한다.

 

 

 

'Virtual Devices'를 클릭하고 새로 만들어줄 가상장치의 이름을 입력하고, Target을 'Andriod 2.2 -  API Level 8'로
설정한 후, SD Card 항목에서는 '9'(나중에 적당한 값으로 바꾸면 된다), Skin은 'WVGA800'을 추천한다. (예제에서는
캡쳐 편의상 400을 골랐다)

 

 

 

하드웨어 항목에 대해서 잘 모르겠다면 더 이상 추가할 장치가 나오지 않을 때까지 'New'를 클릭해준다.
센서들이나 GPS는 별 소용 없고 추가 앱의 설치 테스트를 위해서 장치의 내장 메모리의 용량 정도만 설정해주면 된다.

 

 

 

OK 클릭.

 

 

 

만들어진 가상장치를 클릭하고 Start...를 클릭.

 

 

 

모니터 화면에 보여줄 해상도를 설정하는 부분인데, 그냥 Launch를 클릭.

 

 

 

부팅에는 실제 폰과 비슷한 시간이 소요되고.. 스킨도 나중에 config.ini파일을 편집해서 바꿀 수 있다. 

 

 

 

부팅이 되면 어플리케이션 버튼을 클릭하자.

 

 

 

마켓을 클릭.

 

 

 

마켓에 접속하려면 실제 폰과 같이 간단한 등록 과정을 거쳐야 한다.
유료 앱은 결제도 되지만 실제로 신용카드로 결제가 되기 때문에 돈이 나간다는 것을 명심하자.

 

 


                                                                       안드로이드 마켓에 접속되었다.

짬이 날 때 마다 검색으로 마켓이 가능한 에뮬레이터를 찾다보니 아예 안드로이드 앱 개발키트를 모두 설치하게 되었는데, Android-SDK는 허접의 극을 달린다.  특히 느린 반응 속도는 이게 과연 개발툴이 맞는지 의심스러울 정도고, 마켓의 사용도 일부만 가능하다. eclipse를 사용하지 않고서도 이런 툴로 앱을 개발할 수 있기는 한 것일까? ^^;

괜찮은 안드로이드 팁사이트

http://blog.naver.com/dbrwhdqja

android : View 에 있는 것을 Bitmap으로 저장 / Bitmap으로 된 것을 읽기  program

2010/07/27 23:22

복사http://utime.blog.me/150090888234


현재 View 클래스에 보여지는 화면을 파일로 저장하는 클래스다.
View를 상속 받아 만든 클래스 ImageView, WebView 등 클래스를 이용할 수 있다.

 
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Picture;
import android.graphics.drawable.PictureDrawable;
import android.content.Context;
import android.view.View;
import android.widget.ImageView;
import android.util.Log;

public class ImageUtil {

      private static final String LOGTAG = "ImageUtil";
      
      /*
       * Picture를 Bitmap 파일로 저장
       * viw : View ...
       * fn : 파일 이름
       * return : 성공 유무
       */
      public static boolean PictureSaveToBitmapFile(View viw, final String fn )
      {
        boolean bRes = false;
        if( viw == null && fn == null && fn.length() < 1 )
          return bRes;

        viw.setDrawingCacheEnabled(true);
        Bitmap bmp =  viw.getDrawingCache();
        if( bmp == null )
            return bRes;

        File file = new File( fn );
        FileOutputStream fOut = null;
        try {
          fOut = new FileOutputStream(file);
        } catch (FileNotFoundException e) {
          Log.e(LOGTAG, e.getMessage());
          return bRes;
        }

        bmp.compress(Bitmap.CompressFormat.PNG, 85, fOut);

        try {
          fOut.flush();
          fOut.close();
          bRes = true;
        } catch (IOException e) {
          Log.e(LOGTAG, e.getMessage());
          return bRes;
        }
        
        return bRes;
      }
      
      
      /*
       * Bitmap 파일을 읽어오기
       * fn : 파일 이름
       * return : bitmap 이미지, 실패했을 경우 null
       */
      public static Bitmap BitmapLoadFromFile( final String fn )
      {
            Bitmap bmp = null;
            try
            {
                bmp = BitmapFactory.decodeFile( fn );
            }catch( Exception e)
            {
                Log.e(LOGTAG, e.getMessage());
            }
            return bmp;
      }
}



사용 예제
레이아웃 main.xml
 
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <Button 
        android:text="Copy" 
        android:id="@+id/Button01" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content"
    />
    <WebView 
        android:id="@+id/WebView01" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content"
    />
    <ImageView 
        android:id="@+id/ImageView01" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content"
    />
</LinearLayout>



 구현 부
 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Picture;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.PictureDrawable;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

public class WebViewCopy extends Activity {
    
    private Button mBtnCopy = null;
    private WebView mSource = null;
    private ImageView mImgView = null;    
    
    private static final String LOGTAG = "UtimeLog";
    private static final String PIC_FILE_ = "PicDump.dat";
    private final static String PATH = "/sdcard/";
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        mBtnCopy = (Button   )findViewById(R.id.Button01);
        mSource  = (WebView  )findViewById(R.id.WebView01);
        mImgView = (ImageView)findViewById(R.id.ImageView01);
        
        mBtnCopy.setOnClickListener( new View.OnClickListener() {
            
            @Override
            public void onClick(View v) {
                OnImageCopyEvent( v );
            }
        });
        
        initWeb();
    }
    
    private void WriteLogMessage( final String sLog )
    {
        Log.i(LOGTAG, sLog);
        Toast.makeText( getApplicationContext(), sLog, Toast.LENGTH_SHORT).show();
    }
    
    private void initWeb() {
        mSource.setWebViewClient(new WebViewClient());
        mSource.getSettings().setJavaScriptEnabled(true);

        mSource.loadUrl("http://www.google.com/");
    }
    
    private void OnImageCopyEvent( View v )
    {
        String fn = "puhaha.dmp";
        ImageUtil.PictureSaveToBitmapFile( mSource,
                                            fn);
        
        Bitmap bmp = null;
        bmp = ImageUtil.BitmapLoadFromFile(fn);
        mImgView.setImageBitmap(bmp);

    }
}

출처 : 

http://blog.naver.com/sancholok?Redirect=Log&logNo=30091202062


개발환경 : JDK 1.5, eclipse-galileo, android googleAPI 7, window XP

모든 프로그램에서 이미지 관리의 기본은 비트맵이다안드로이드에서도 마찬가지로

이미지 관리와 표현을 위해서는 비트맵을 익히는게 가장 기본이다그 비트맵 관련

내용들을 소개한다.

 

안드로이드에서 비트맵 관련 클래스는 android.graphics.Bitmap 이다그래픽 관련

클래스들은 android.graphics 패키지에 있으며 여기에 포함된 것이다.

그리고 객체 Factory 관리를 위한 BitmapFactory 클래스가 있다. BitmapFactory 

여러가지 이미지 포맷을 decode 해서 bitmap 으로 변환하는 함수들로 되어있는데

그 이름들은 decodeXXX 로 되어있어서 쉽게 원하는 기능의 함수를 찾을수 있을

것이다.

 

(1) BitmapFactory 에서 주로 사용하고 있는 함수와 옵션에 대한 설명


BitmapFactory.decodeByteArray() : Camera.PictureCallBack  으로부터 받은 Jpeg 사진

데이터를 가지고 Bitmap  으로 만들어 줄 때 많이 사용한다.

Camera.PictureCallback 에서 들어오는 데이터가 byte[] 배열로 들어오기 때문이다.

 

BitmapFactory.decodeFile() : 로컬에 존재하는 파일을 그대로 읽어올 때 쓴다파일경로를

파라미터로 넘겨주면 FileInputStream 을 만들어서 decodeStream 을 한다.

1 Bitmap orgImage = BitmapFactory.decodeFile(“/sdcard/test.jpg”);

BitmapFactory.decodeResource() : Resource 폴더에 저장된 그림파일을 Bitmap 으로

만들어 리턴해준다

 
1 Bitmap orgImage =
2      BitmapFactory.decodeResource(getResources(), R.drawable.test02);

BitmapFactory.decodeStream() : InputStream 으로부터 Bitmap 을 만들어 준다.

 

BitmapFactory.Options : BitmapFactory 가 사용하는 옵션클래스이다. Options 객체를 생성하고

설정하고자 하는 옵션을 넣은후 BitmapFactory 의 함수 실행시 파라미터로 넘기면된다.

inSampleSize : decode 시 얼마나 줄일지 설정하는 옵션인데 1보다 작을때는 1이 된다.

1보다 큰값일 때 1/N 만큼 이미지를 줄여서 decoding 하게 된다보통 2의 배수로 설정한다.

 
1 BitmapFactory.Options options = new BitmapFactory.Options();
2 options.inSampleSize = 4;
3 Bitmap orgImage = BitmapFactory.decodeFile(“/sdcard/test.jpg”, options);

 

(2) Bitmap 과 BitmapFactory 을 사용한 여러가지 예제 


BitmapFactory 로 이미지를 읽어온뒤 Bitmap.createScaledBitmap() 사용해서 크기를 재조정

할수 있다하지만 예를 들어 크기를 일정하게 2, 4 배등으로 줄일거면 굳이createScaledBitmap

을 사용하지 않고 위에서 설명한 BitmapFactory.Options  inSampleSize 를 사용하면 된다.

아래는 SD 카드에서 이미지를 불러와 Bitmap 을 원하는 크기 만큼 줄인 예제이다.

1 Bitmap orgImage = BitmapFactory.decodeFile(“/sdcard/test.jpg”);
2 Bitmap resize = Bitmap.createScaledBitmap(orgImage, 300, 400, true);

다음은 BitmapFactory.Options 사용해서 이미지를 4배로 줄인것인데 createScaledBitmap 

사용해서 용량을 줄인 이미지에 다시 입력한 크기만큼 가로세로 크기를 줄인 것이 된다

1 BitmapFactory.Options options = new BitmapFactory.Options();
2 options.inSampleSize = 4;
3 Bitmap orgImage = BitmapFactory.decodeFile(“/sdcard/test.jpg”, options);
4 Bitmap resize = Bitmap.createScaledBitmap(orgImage, 300, 400, true);

google_protectAndRun("ads_core.google_render_ad", google_handleError, google_render_ad);

 

 

[OutOfMemoryError??]

 

보통 이미지 파일을 읽어서 Resizing을 해야 할 때가 있는데, 
그럴때는 BitmapFactory로 읽어서 Bitmap.createScaledBitmap() 메소드를 사용하여 줄이면

간단하게 처리 할 수 있습니다.

 

그런데 BitmapFactory를 사용할 때 주의해야 할 점이 있습니다.
아래의 예를 한번 보시죠.

Bitmap src = BitmapFactory.decodeFile("/sdcard/image.jpg");
Bitmap resized = Bitmap.createScaledBitmap(src, dstWidth, dstHeight, true);

이미지 파일로부터 Bitmap을 만든 다음에

다시 dstWidth, dstHeight 만큼 줄여서 resized 라는 Bitmap을 만들어 냈습니다.
보통이라면 저렇게 하는게 맞습니다.

 

읽어서, 줄인다.

 

그런데 만약 이미지 파일의 크기가 아주 크다면 어떻게 될까요?
지금 Dev Phone에서 카메라로 촬영하면
기본적으로 2048 x 1536 크기의 Jpeg 이미지가 촬영된 데이터로 넘어옵니다.
이것을 decode 하려면 3MB 정도의 메모리가 필요 할 텐데,

과연 어떤 모바일 디바이스에서 얼마나 처리 할 수 있을까요?

 

실제로 촬영된 Jpeg 이미지를 여러번 decoding 하다보면

아래와 같은 황당한 메세지를 발견 할 수 있습니다.

java.lang.OutOfMemoryError: bitmap size exceeds VM budget

네... OutOfMemory 입니다.
더 이상 무슨 말이 필요 하겠습니까...
메모리가 딸려서 처리를 제대로 못합니다.

 

이것이 실제로 decoding 후 메모리 해제가 제대로 되지 않아서 그런 것인지, 
하더라도 어디서 Leak이 발생 하는지에 대한 정확한 원인은 알 수 없습니다.
이것은 엔지니어들이 해결해야 할 문제 겠죠...

 

하지만 메모리 에러를 피할 수 있는 방법이 있습니다.

 


[BitmapFactory.Options.inSampleSize]

 

BitmapFactory.decodeXXX 시리즈는 똑같은 메소드가 두 개씩 오버로딩 되어 있습니다.
같은 이름이지만 Signature가 다른 메소드의 차이점은
BitmapFactory.Options를 파라메터로 받느냐 안받느냐의 차이죠.

BitmapFactory.Options를 사용하게 되면 decode 할 때 여러가지 옵션을 줄 수 있습니다.


여러가지 많지만 저희가 지금 사용할 것은 inSampleSize 옵션 입니다.

 

inSampleSize 옵션은,
애초에 decode를 할 때 얼마만큼 줄여서 decoding을 할 지 정하는 옵션 입니다.

 

inSampleSize 옵션은 1보다 작은 값일때는 무조건 1로 세팅이 되며,
1보다 큰 값, N일때는 1/N 만큼 이미지를 줄여서 decoding 하게 됩니다.
즉 inSampleSize가 4라면 1/4 만큼 이미지를 줄여서 decoding 해서 Bitmap으로 만들게 되는 것이죠.

 

2의 지수만큼 비례할 때 가장 빠르다고 합니다.
2, 4, 8, 16... 정도 되겠죠?

 

그래서 만약 내가 줄이고자 하는 이미지가 1/4보다는 작고 1/8보다는 클 때,
inSampleSize 옵션에 4를 주어서 decoding 한 다음에,

Bitmap.createScaledBitmap() 메소드를 사용하여 한번 더 줄이면 됩니다.

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
Bitmap src = BitmapFactory.decodeFile("/sdcard/image.jpg", options);
Bitmap resized = Bitmap.createScaledBitmap(src, dstWidth, dstHeight, true);

당연한 이야기 이겠지만,
내가 원하고자 하는 사이즈가 딱 1/4 크기라면

Bitmap.createScaledBitmap() 메소드를 쓸 필요가 없지요.

 

inSampleSize 옵션을 잘 활용하면 메모리 부족 현상을 대략적으로 해소 할 수 있습니다.
참고로 제가 저 옵션을 사용한 뒤로는 메모리 에러를 본적이 한~번도 없답니다.

 


[Appendix]

 

inSampleSize 옵션을 사용하면

SkScaledBitmapSampler Object (Library Level) 를 생성 하게 되는데,
Object를 만들때 정해진 SampleSize 만큼 축소하여 width와 height를 정한 뒤에 만들게 됩니다.
그러니까 애초에 축소된 사이즈로 이미지를 decoding 하는 것이죠.

//참고로 setWallPaper는 권한으로 인해 예외가 남..주석처리 하세영..

//100프로 실행됨..최고의 소스...2틀동안 고생하다 발견한 주옥같은 소스..T.T 난 바보양..


/ 소스는 밑에 굵게 표시된 부분임..

 

 

< How to save a canvas to disk Options >


http://groups.google.com/group/android-developers/browse_thread/thread/6aeed20525760f3/46407ecb1c68502e?hl=en&lnk=gst&q=canvas+to+file#46407ecb1c68502


Hi, 
I am doing a painting program (KIds Paint - you can find in Android 
Market) and I have a lot of requests to save the content on disk or to 
wallpaper. I have been searching around but cannot find solution. 
My guess is that I probably wanted to get the bitmap from the canvas, 
but I can't find ways to get it. Then I try to set an empty bitmap 
into the canvas and draw on the canvas, and save the bitmap... but I 
got an empty bitmap. 
Please help! Thanks. Here's my codes: 
public class KidsPaintView extends View { 
        Bitmap bitmap = null; 
... 
 protected void onDraw(Canvas canvas) { 
        if (bitmap == null) { 
            bitmap = Bitmap.createBitmap(width, height, 
Bitmap.Config.ARGB_8888); 
            canvas.setBitmap(bitmap); 
        } 
  ... // do painting on canvas 
  } 
}

Then in my main code I try to retrieve the bitmap and save it as 
wallpaper: 
Bitmap bitmap = view.bitmap; 
try { setWallpaper(bitmap); } 
catch (IOException e) { e.printStackTrace(); } 
But all I got is a black wallpaper.

    Reply to author     Forward        Report spam     Rate this post:  
  
gjs   
View profile  
 More options Sep 23 2009, 3:23 pm
Hi, 
Have a look at - 
http://developer.android.com/reference/android/view/View.html#draw(an...) 
- create a new bitmap and a new canvas, associate the bitmap with this 
canvas and call view.draw( your_new_canvas ) - using your 
KidsPaintView view instance - then save the bitmap or use it to set 
the wallpaper. 
Regards 
On Sep 23, 1:03 pm, limtc <thyech...@gmail.com> wrote:

- Show quoted text -

    Reply to author     Forward        Report spam     Rate this post:  
  
limtc   
View profile  
 More options Sep 23 2009, 5:12 pm
Mmm... I don't quite understand. The link you sent is the 
documentation for View? 
If I have a new canvas, what happened to the canvas in onDraw? And 
when should I call view.draw(canvas)? 
Do you have any sample codes? Appreciated.


    Reply to author     Forward        Report spam     Rate this post:  
  
gjs   
View profile  
 More options Sep 24 2009, 9:12 pm
Hi, 
I made up the following to demonstrate what I meant. 
In this example when you press the trackball/dpad center button the 
view is written to a png file and also set as the wallpaper. 
You can change it to save as jpeg file etc. 
Hope that helps. 
Regards 
///////////////////////////////////////////// 
package com.testSaveView; 
import android.app.Activity; 
import android.os.Bundle; 
import java.io.FileOutputStream; 
import android.content.Context; 
import android.graphics.Bitmap; 
import android.graphics.Canvas; 
import android.graphics.Color; 
import android.graphics.Paint; 
import android.util.Log; 
import android.view.KeyEvent; 
import android.view.View; 
public class testSaveView extends Activity 

        SomeView sv = null; 
    /** Called when the activity is first created. */ 
    @Override 
    public void onCreate(Bundle savedInstanceState) 
    { 
        super.onCreate(savedInstanceState); 
        sv = new SomeView(this); 
        setContentView( sv ); 
    } 
    @Override 
    public boolean onKeyDown( int keyCode, KeyEvent event) 
        { 
        switch( event.getKeyCode() ) 
                { 
                case KeyEvent.KEYCODE_DPAD_CENTER: 
                        if ( sv != null ) 
                        { 
                                saveView( sv ); 
                                return true; 
                        } 
                default: 
                } 
                return super.onKeyDown( keyCode, event ); 
        } 
    private void saveView( View view ) 
    { 
       Bitmap  b = Bitmap.createBitmap( view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); 
       Canvas c = new Canvas( b ); 
       view.draw( c ); 
       FileOutputStream fos = null; 
       try { 
                fos = new FileOutputStream( "/sdcard/some_view_image_" + System.currentTimeMillis() + ".png" ); 
                if ( fos != null ) 
                { 
                        b.compress(Bitmap.CompressFormat.PNG, 100, fos ); 
                        fos.close(); 
                } 
                setWallpaper( b ); 
            } catch( Exception e ) 
                        { 
                        Log.e("testSaveView", "Exception: " + e.toString() ); 
                        } 
    } 
class SomeView extends View 

        public SomeView( Context context ) 
        { 
          super( context ); 
        } 
        public void onDraw( Canvas canvas ) 
        { 
                canvas.drawARGB(0x80, 0xff, 0, 0 ); 
                Paint paint = new Paint(); 
                paint.setColor(Color.BLUE); 
                paint.setTextSize( 48 ); 
                canvas.drawText("...Some view...", 10, canvas.getHeight() / 2, 
paint); 
        } 

}

////////////////////////// 
& the manifest - 
<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android
      package="com.testSaveView" 
      android:versionCode="1" 
      android:versionName="1.0"> 
    <application android:icon="@drawable/icon" android:label="@string/ 
app_name"> 
        <activity android:name=".testSaveView" 
                  android:label="@string/app_name"> 
            <intent-filter> 
                <action android:name="android.intent.action.MAIN" /> 
                <category 
android:name="android.intent.category.LAUNCHER" /> 
            </intent-filter> 
        </activity> 
    </application> 
    <uses-sdk android:minSdkVersion="3" /> 
<uses-permission android:name="android.permission.SET_WALLPAPER"></ 
uses-permission> 
</manifest> 
On Sep 23, 7:12 pm, limtc <thyech...@gmail.com> wrote:

- Show quoted text -

    Reply to author     Forward        Report spam     Rate this post:  
  
limtc   
View profile  
 More options Sep 24 2009, 10:45 pm
Thanks! 
I think the part that I am confused is that I never used view.draw 
(canvas) code in my program so I don't know when to call it... at this 
moment, says I wanted to clear a screen, this is my code in the view: 
    public void clearScreen() { 
        gradient = false; 
        dots.clear(); 
        invalidate(); // this will call the onDraw and pass in the 
canvas 
    } 
so I replace invalidate() by draw(canvas)?


    Reply to author     Forward        Report spam     Rate this post:  
  
gjs   
View profile  
 More options Sep 25 2009, 3:39 pm
Hi, 
That is why I gave you a complete working example ( maybe try it 
yourself ). But not knowing your code I could only guess about when 
you would want to call the saveView() method I provided. 
I guess that you would call the saveView() method when the user 
requests that your KidsPaintView view should save their 'drawing' 
either to a file or to set as the wallpaper. 
I imagine that you would provide a menu option button, or some 
touchscreen or keyboard button to allow the user to trigger that 
action - when they had finished making their 'drawing'. I just used 
the trackball/dpad center button as an example to demonstrate 
triggering and saving the view. 
In my example I used 'SomeView' to represent your KidsPaintView and 
put the saveView() method in the Activity, but you could also put the 
saveView() method or some equivalent code inside your KidsPaintView 
view if you wish. 
You should also realize that you CAN just call draw( canvas ) on your 
KidsPaintView as your class extends View so it (automatically) 
inherits the View.draw( canvas ) method... You just need to ensure 
that you create and use a new canvas and a new bitmap when calling the 
draw( canvas) method, as I said originally and demonstrated with the 
code posted. 
Good luck ! 
Regards 
On Sep 25, 12:45 am, limtc <thyech...@gmail.com> wrote:

- Show quoted text -

    Reply to author     Forward        Report spam     Rate this post:  
  
limtc   
View profile  
 More options Sep 29 2009, 4:03 pm
Thanks! 
After experiementing, seems to work. I think all I need to do is to do 
the view.draw(canvas) prior to have the canvas filled with what's 
drawn on screen. I am still exactly sure what's happening though, as 
basically I just do everything as it is, and create a new bitmap and a 
new canvas associate with it, and prior to saving, pass the new canvas 
to the view. 
About view.draw(canvas): 
http://developer.android.com/reference/android/view/View.html#draw(an...) 
Anyway, I am happy! 

출처 : 

http://blog.naver.com/crowdark7?Redirect=Log&logNo=108184807



이벤트 핸들러 - 여러 가지 이벤트

 

요약

터치입력 이벤트콜백 메서드 처리

키보드입력 이벤트콜백 메서드 처리, keyCode속성, KeyEvent속성

위젯 이벤트리스너 구현 (버튼 별 구현 -> 뷰에 구현해 통합 -> 리스너 객체 선언)

포커스 관리모드 별 포커스 설정일반 모드에서 포커스 이동강제 포커스 요청포커스 이벤트

 

터치 입력

 

 터치 입력이란 손가락이나 스타일러스 펜으로 화면을 누르는 터치 동작을 말한다.

 터치 입력에 대한 콜벡 메서드는 해당 뷰에서 재정의 하므로 이벤트 정보만을 가지지만리스너는 여러 대상에 대해 등록이 가능하기 때문에 이벤트 대상인 v를 전달 받는다.

 

 만약 뷰의 onTouchEvent 콜백 메서드가 처리하지 않았을 경우 액티비티의 콜백이 호출된다.

 두 방법의 차이점은 뷰는 뷰에서의 좌표로 인수가 전달되지만 액티비티는 액티비티의 좌상단을 기준으로 인수가 전달되어 오차가 생길 가능성이 크다. (타이틀 바가 있기 때문에)

 

콜백 메서드 이용

boolean onTouchEvent(MotionEvent event)

인터페이스 이용

boolean onTouch(View v, MotionEvent event)

 

위의 MotionEvent 객체는 두 경우 모두 동일한 역할을 하는데 MotionEvent.getAction 메서드는 사용자가 화면에 대고 어떤 것을 했는지에 대한 정보를 전달한다.

 

MotionEvent의 속성

ACTION_DOWN

화면을 누름

ACTION_MOVE

누른채로 움직였다.

ACTION_UP

화면에서 손가락을 뗐다.

 

아래의 코드는 터치하여 누른 채로 움직였을 경우 그 점들을 선으로 이어주는 코드 중 하나이다. onTouchEvent를 재정의 한 것으로 arVertex라는 ArrayList에 이벤트가 일어난 곳의 좌표를 저장하는 과정이다.

 

 

public boolean onTouchEvent(MotionEvent event) {

 

// getAction을 이용해서 현재 어떤 상태인지 알아내고 그 상태가 화면을 누른 것이라면

// 누른 곳의 좌표를 받아서 ArrayList에 저장한다.

 

                     if (event.getAction() == MotionEvent.ACTION_DOWN) {

                               arVertex.add(new Vertex(event.getX(), event.getY(), false));

                               return true;

                     }

 

// 누른 채로 움직였다면 좌표를 추가하고화면에 표시해주기 위해서 invalidate를 이용한다이는 무효화를 통해서 다시 onDraw를 호출하는 기능을 한다.

 

                     if (event.getAction() == MotionEvent.ACTION_MOVE) {

                               arVertex.add(new Vertex(event.getX(), event.getY(), true));

                               invalidate();

                               return true;

                     }

                     return false;

               }

 

 

키보드 입력

 

 모바일 장비에는 문자 입력을 위한 키보드가 필요한데 쿼티 자판을 가진 장비도 있고 간단한 단추만을 가진 장비도 있다이런 키보드를 누를 때의 이벤트는 아래와 같은 메서드가 처리한다.

 

 만약 뷰에서 키 입력을 처리 하지 않으면 액티비티의 콜백 메서드가 처리하게 되어있다키 입력의 경우 뒤로 버튼까지 액티비티의 핸들러가 받아버리므로 특별히 처리해야 하는 곤란함이 있어서 가능하면 뷰에서 처리하는 것이 좋다.

 

콜백 메서드 이용

boolean onKeyDown(int keyCode, keyEvent event)

인터페이스 이용

boolean onKey (View v, int keyCode, keyEvent event)

 

keyCode의 속성

KEYCODE_DPAD_LEFT

좌측 이동

KEYCODE_DPAD_RIGHT

우측 이동

KEYCODE_DPAD_UP

위 이동

KEYCODE_DPAD_DOWN

아래 이동

KEYCODE_DPAD_CENTER

중앙버튼

KEYCODE_A

알파벳 키(A to Z)

KEYCODE_0

숫자 키(0~9)

KEYCODE_CALL

통화

KEYCODE_ENDCALL

통화종료

KEYCODE_HOME

KEYCODE_BACK

뒤로

KEYCODE_VOLUME_UP

볼륨 증가

KEYCODE_VOLUME_DOWN

볼륨 감소

 

KeyEvent의 속성

ACTION_DOWN

키를 눌렀음

ACTION_UP

키를 뗐음

ACTION_MULTIPLE

같은 키를 여러 번 눌름

 

아래의 경우는 콜벡 메서드를 재정의하는 경우이다방향 패드를 누를 때마다 mX 속성을 그에 맞게 바꾸게 된다.

 

 

public boolean onKeyDown(int KeyCode, KeyEvent event) {

                                super.onKeyDown(KeyCode, event);

                                if (event.getAction() == KeyEvent.ACTION_DOWN) {

                                          switch (KeyCode) {

                                          case KeyEvent.KEYCODE_DPAD_LEFT:

                                                     mX-=5;

                                                     invalidate();

                                                     return true;

                                          case KeyEvent.KEYCODE_DPAD_RIGHT:

                                                     mX+=5;

                                                     invalidate();

                                                     return true;

                                          case KeyEvent.KEYCODE_DPAD_UP:

                                                     mY-=5;

                                                     invalidate();

                                                     return true;

                                          case KeyEvent.KEYCODE_DPAD_DOWN:

                                                     mY+=5;

                                                     invalidate();

                                                     return true;

                                          case KeyEvent.KEYCODE_DPAD_CENTER:

                                                     if (mColor == Color.BLUE) {

                                                                mColor = Color.RED;

                                                     } else {

                                                                mColor = Color.BLUE;

                                                     }

                                                     invalidate();

                                                     return true;

                                          }

                                }

                                return false;

                     }

 

 

위젯의 이벤트 처

 

 위젯을 등록할 때는 해당 위젯의 클래스를 사용하는 것이 보통이다. 정해진 위젯의 클래스를 이용하기 때문에 상속을 받지 않고 이벤트를 처리할 수 있어야 하므로 인터페이스를 이용해 리스너로 이벤트를 받아야 한다.

 위젯의 경우 터치와 다르게 어느 좌표에서 터치가 이루어 졌는지 등에 대한 정보가 필요 없으므로 클릭된 뷰를 전달하는 것 외에는 별도의 인수가 없다.

 

 아래는 버튼을 클릭하였을 때 이벤트 처리를 구현한 코드 예제이다각 버튼에 대해서 클릭 이벤트를 정의하였다그런데 버튼이 여러 개일 경우 하는 일은 비슷한데 모두 클릭이벤트를 정해야 한다면 상당히 불편할 것이다.

 

버튼 별 인터페이스 구현

 

public class Fruit extends Activity {

           public void onCreate(Bundle savedInstanceState) {

                     super.onCreate(savedInstanceState);

                     setContentView(R.layout.input_fruit);

 

                     Button btnApple=(Button)findViewById(R.id.apple);

                     btnApple.setOnClickListener(new Button.OnClickListener() {

                                public void onClick(View v) {

                                          TextView textFruit=(TextView)findViewById(R.id.fruit);

                                          textFruit.setText("Apple");

                                }

                     });

 

                     Button btnOrange=(Button)findViewById(R.id.orange);

                     btnOrange.setOnClickListener(new Button.OnClickListener() {

                                public void onClick(View v) {

                                          TextView textFruit=(TextView)findViewById(R.id.fruit);

                                          textFruit.setText("Orange");

                                }

                     });

           }

}

 

뷰에 통합 + 리스너 객체 선언

 비슷한 코드가 반복 되기 때문에 통합한 코드가 바로 아래의 코드이다안드로이드에서는 하나의 리스너를 여러 뷰에 등록하는 것을 허락하기 때문에 통합이 가능하다또한 액티비티는 그대로 두고 리스너 객체를 멤버로 선언한 후에 이것을 리스너로 사용하는 것이 좋다.

(this --> mClickListener)

 

public class Fruit extends Activity {

           public void onCreate(Bundle savedInstanceState) {

                     super.onCreate(savedInstanceState);

                     setContentView(R.layout.input_fruit);

 

                     findViewById(R.id.apple).setOnClickListener(mClickListener);

                     findViewById(R.id.orange).setOnClickListener(mClickListener);

           }

 

           Button.OnClickListener mClickListener = new View.OnClickListener() {

                     public void onClick(View v) {

                                TextView textFruit=(TextView)findViewById(R.id.fruit);

                                switch (v.getId()) {

                                case R.id.apple:

                                          textFruit.setText("Apple");

                                          break;

                                case R.id.orange:

                                          textFruit.setText("Orange");

                                          break;

                                }

                     }

           };

}

 

 

포커스 관리

 

 키보드 이벤트는 포커스를 가지고 있는 뷰에게만 전달이 된다그리고 특정 시점에서 입력을 받을 수 있는 뷰는 하나밖에 없다그래서 포커스는 입력을 받을 뷰가 어떤 것인지 가리키며 다른 뷰와 다르게 표시한다.

 안드로이드에선 일반적으로 버튼은 주황색으로 표현하며 에디트는 경계선이 굵은 주황색으로 표시한다.

 

안드로이드는 포커스를 표시할 때 모드 별로 차이가 있다.

터치 모드에서는 포커스를 표시하지 않고

일반 모드일 때는 포커스를 표시한다일반 모드는 키를 이용해서 이동하게 되는 경우이다.

 

 현재 터치모드인지를 확인하는 메서드는 IsInTouchMode가 있다이 메서드로 현재의 상황을 조사하고 위젯에 포커스가 가능한지 아닌지를 정할 수 있다.

 

모드

XML속성

포커스 설정

포커스 조사

일반

focusable

setFocusable

isFocusable

터치

focusableInTouchMode

setFocusableInTouchMode

isFocusableInTouchMode

 

 만약 위의 setFocusable setFocusableInTouchMode를 생략하면 해당 뷰가 포커스를 받지 못하므로 키 입력이벤트는 전달되지 않는다. setFocusableInTouchMode는 없어도 된다고 생각할 수도 있으나 일반 모드에서 터치 모드로 변경될 때를 위해서 필요하다.

 

일반 모드에서 포커스 이동방식

 

디폴트

키보드를 이용해서 이동하게 되면 가장 가까운 위젯으로 포커스를 옮기게 되어 있다.

최초 실행 시에 터치모드라면 아무도 포커스를 가지지 않고

일반 모드라면 첫 번째 위젯이 포커스를 가진다.

 

특정 이동 지정

속성

메서드

설명

nextFocusLeft

setNextFocusLeftId

왼쪽 이동 시의 위젯

nextFocusRight

setNextFocusRightId

오른쪽 이동 시의 위젯

nextFocusUp

setNextFocusUpId

위쪽 이동 시의 위젯

nextFocusDown

setNextFocusDownId

아래쪽 이동 시의 위젯

 

특정 뷰로 강제 포커스 이동

이럴 경우는 원하는 뷰의 requestFocus 메서드를 호출하면 된다.

포커스 변경 시에는 onFocusChanged이벤트가 발생하는데 포커스가 변경하게 될 경우에 수행하게 되는 동작을 처리한다포커스 이동을 하게 되면 별표로 강조를 하는 경우이 이벤트 핸들러를 사용해서 변경시키는 것이다.

 

*본 포스트는 김상형 저, <안드로이드 프로그래밍 정복>, 한빛미디어를 참고하였습니다. 

책에서는 더 많은 내용을 보실 수 있습니다. 안적은 것들이 많아요 ㅠㅠ

http://blog.naver.com/skcjs84?Redirect=Log&logNo=90089483420

http://stackoverflow.com/questions/3107527/android-save-view-to-jpg-or-png

밑의 예제는 null포인터 오류남..쓰불..도저히 모르겠다..


would like to write an android app that basically layers an overlay on image on another image and then I would like to save the picture with the overlay as a jpg or png. Basically this will be the whole view that I would like to save.

Sample code would be very helpful.

EDIT:

I tried out your suggestions and am getting a null pointer at the Starred Line.

 import java.io.File; 
import java.io.FileNotFoundException; 
import java.io.FileOutputStream; 
 
import android.app.Activity; 
import android.graphics.Bitmap; 
import android.graphics.Bitmap.CompressFormat; 
import android.os.Bundle; 
import android.os.Environment; 
import android.widget.LinearLayout; 
import android.widget.TextView; 
 
    public class EditPhoto extends Activity { 
        /** Called when the activity is first created. */ 
     LinearLayout ll = null; 
     TextView tv = null; 
        @Override 
        public void onCreate(Bundle savedInstanceState) { 
            super.onCreate(savedInstanceState); 
            setContentView(R.layout.main); 
            tv = (TextView) findViewById(R.id.text); 
            ll = (LinearLayout) findViewById(R.id.layout); 
            ll.setDrawingCacheEnabled(true); 
            Bitmap b = ll.getDrawingCache(); 
            File sdCard = Environment.getExternalStorageDirectory(); 
            File file = new File(sdCard, "image.jpg"); 
            FileOutputStream fos; 
      try { 
       fos = new FileOutputStream(file); 
       *** b.compress(CompressFormat.JPEG, 95,fos); 
      } catch (FileNotFoundException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 
 
        } 
    } 
link|flag

53% accept rate
Nothing on this? I know it is possible, I have seen it done in other apps. – dweebsonduty Jun 25 at 4:41
Can you give us some code describing how you are doing the editing? – Chris Thompson Aug 19 at 20:36
Its more of an idea right now, but I will just have an imageview overlayed over the photo imageview. Unless there is a better way to do it. – dweebsonduty Aug 19 at 20:38
Are you wanting to save the image with the layers (ala PSD) or just as a flat image (ala png)? – Chris Thompson Aug 19 at 20:59
One flat png or jpg would be fine. – dweebsonduty Aug 19 at 22:31

2 Answers

up vote 1 down vote accepted
+150

You can take advantage of a View's drawing cache.

view.setDrawingCacheEnabled(true); 
Bitmap b = view.getDrawingCache(); 
b.compress(CompressFormat.JPEG, 95, new FileOutputStream("/some/location/image.jpg")); 

Where view is your View. The 95 is the quality of the JPG compression. And the file output stream is just that.

link|flag
I tried your code, but where does the root starts for the FileOutputStream? beacause I tried to look in the folders of the emulator and couldn't find the saved image... – Sephy Aug 21 at 12:21
1  
It starts at the phones root. So if you want to load something from the sdcard, use the Environment.getExternalStorageDirectory() to get the root for the sdcard. – Moncader Aug 21 at 15:38
So can a LinerLayout be the view in this case and will that grab anything in the LinearLayout? – dweebsonduty Aug 23 at 21:16
@dweebsonduty, Yup that's right. – Moncader Aug 24 at 1:27
It looks like it works thanks and to you goes almost all of my rep :). Thanks guys!! – dweebsonduty Aug 24 at 1:38
up vote 0 down vote
File sdCard = Environment.getExternalStorageDirectory(); 
File file = new File(sdCard, "image.jpg"); 
FileOutputStream fos = new FileOutputStream(file); 

Use fos reference as a 3rd parameter of b.compress() method in Moncader's answer. The image will be stored as image.jpg in root directory of your sd card.

link|flag

출처 : http://luxtella.tistory.com

결론은 두가지 방법이 있다.
1. View의 canvas가 그림을 framebuffer가 아닌 bitmap에 그리게 하기
2. View.getDrawingBuffer() 이용하기

우선 성능 측면에서는 1번이 약간 앞선다. 두가지 방법의 메카니즘을 설명해보자

1. View의 canvas가 그림을 framebuffer가 아닌 bitmap에 그리게 하기

우선 사용법은

View를 상속받아  onDraw를 overriding하면 그림을 screen(framebuffer겠죠?)에 그리지 않고 bitmap에 그릴수 있다

public void onDraw(Canvas canvas){
canvas.setBitmap(bitmap);
bitmap.eraseColor(Color.WHITE);
super.onDraw(canvas);
}

위의 코드처럼 bitmap을 하나 잡아서 그것을 canvas에 장착시키면 그림을 bitmap에 그린다. screen에는 안그리므로 까만화면이 나올 뿐이다.

View ----------- canvas ----framebuffer
              Draw                     |---bitmap

즉 canvas 가 그리는 곳을 bitmap으로 살짝 틀어줌으로서 그림이 그려진 memory를 얻을수 있다.
참고로 Draw가 onDraw를 다시 콜한다. 지난 포스트를 참고하자.


2. View.getDrawingBuffer() 이용하기

우선 getDrawingCache의 내부구현을 보면
View.getDrawingCache
                canvas = softref.get(); //cavas를 softReference로 만들어놓고 쓸때 꺼냅니다. 
                bitmap = createBitmap(width, height, config); //이놈이 jni로 c로 가서 SkBitmap을 새로 alloc합니다. 가장 병목인 곳
canvas.setBitmap(bitmap);
bitmap.eraseColor(Color.WHITE);
draw(canvas);
즉 bitmap공간을 새로 잡고 그것을 별도로 사용하는 canvas에 붙여서 그림을 bitmap에 직접 그린다. 전자와 mechanism은 같지만 bitmap을 새로 alloc한다는 단점이 있다.

앞에 그림에서 앞에 몇가지 과정이 더붙은 모양세다

View ---------------------------------- View ----------- canvas ---- bitmap
          getDrawingCache -- canvas.setBitmap                        Draw 

원래 View의 그림도 그린다면 getDrawingCache다 View.draw를 콜하고 원래 그림을 그리는 쓰래드도 View.draw를 콜하므로 똑같은 그림을 그리기 위해 두번 연산하게 된다.



참고로
 - softReference란 cache를 만들때 java에서 잘 사용하는 기법인데, 오직 메모리가 모자를때 garbage collector에 의해 메모리가 해제됩니다. 즉 메모리를 많이 차지하면서 계속써야되는놈을 softReference로 만들어놓으면 out of memory로 app이 중지하는것을 방지할 수 있습니다.
 - Android의 canvas, bitmap, paint 등의 그림을 그리는 class들이 skia의 SkCanvas, SkBitmap, SkPaint들과 거의 1:1로 매핑되어있네요.
출처 : http://luxtella.tistory.com/entry/Android-ViewgetDrawingCache-%EB%B6%84%EC%84%9D

getDrawingCache가 어떻게 그림을 복사하는지 내부 mechanism을 살펴보자

우선 사용예제는
 webView.setDrawingCacheEnabled(true);
 webView.setDrawingCacheBackgroundColor(Color.WHITE);
 webView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_AUTO);
 webView.getDrawingCache(true)
결론은 c단에서 skia bitmap이 실제로 그려지는 device의 actual pixel memory에 바로 붙어서 그 시점의 memory를 카피하는 것이라 생각된다. 확신을 못하는 이유는 skia쪽 분석을 하다 막혔기 때문이다. 추후에 더 완벽한 분석을 하여 보강하겠다.


frameworks/base/core/java/android/view/View.java 를 보면 getDrawingCache() 가 buildDrawingCache를 콜하면서 일을 한다. buildDrawingCache는 자기가 전에 저장해 놓은 bitmap이 갱신이 않되었으면 그것을 쓰고 아니면 다음을 수행한다.
bitmap = Bitmap.createBitmap(width, height, quality);
이 bitmap을 reference에 저장하여(?) getDrawingCache()로 불러쓴다.
mDrawingCache = new SoftReference<Bitmap>(bitmap);


진짜 일을 하는 놈이 Bitmap.createBitmap 인걸로 밝혀졌으니 그놈은 무엇을 하는지 알아보자
frameworks/base/graphics/java/android/graphics/Bitmap.java 를 살펴보면
public static Bitmap createBitmap(int width, int height, Config config) {
    Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true);
    bm.eraseColor(0);    // start with black/transparent pixels
    return bm;
}
jni로 c 함수를 콜하는것을 알수 있다.
frameworks/base/core/jni/android/graphics/Bitmap.cpp를 살펴보자.
 static JNINativeMethod gBitmapMethods[] = {^M
     {   "nativeCreate",             "([IIIIIIZ)Landroid/graphics/Bitmap;",^M
         (void*)Bitmap_creator },^M
nativeCreate의 구현은 Bitmap_creator이다.
이런 안쪽 구현은 Skia로 해놨군.. 잘 모르니 이제부터 설명은 틀릴가능성이 농후하다....
 static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,^M
                               int offset, int stride, int width, int height,^M
                               SkBitmap::Config config, jboolean isMutable) {^M
//위에서 jColors는 null, offset은 0으로 넘어왔다는것을 기억하자
     if (width <= 0 || height <= 0) {^M
         doThrowIAE(env, "width and height must be > 0");^M
         return NULL;^M
     }^M
 ^M
     if (NULL != jColors) {^M
         size_t n = env->GetArrayLength(jColors);^M
         if (n < SkAbs32(stride) * (size_t)height) {^M
             doThrowAIOOBE(env);^M
             return NULL;^M
         }^M
     }^M
 ^M
     SkBitmap bitmap;^M
 ^M
     bitmap.setConfig(config, width, height);^M
//가장 중요한 부분. bitmap이 actual pixel memory에 access할 수 있게 셋팅하는 부분이다. 부차적으로 bitmap에 메모리 alloc하고 pixel환경(?)을 잡아주는것 같다. VM이 그 메모리를 관리(?)할수 있도록 셋팅해주는것 같은데... 
     if (!GraphicsJNI::setJavaPixelRef(env, &bitmap, NULL, true)) {^M
         return NULL;^M
     }^M
 ^M
//pixel color(?)를 잡아주는것 같은데 우리는  null이 넘어왔으므로 pass 
     if (jColors != NULL) {^M
         GraphicsJNI::SetPixels(env, jColors, offset, stride,^M
                                0, 0, width, height, bitmap);^M
     }^M
 ^M
//결국 return에서 일을 하는 것이군.. SkBitmap는 메모리만 다시 잡아주는듯...
     return GraphicsJNI::createBitmap(env, new SkBitmap(bitmap), isMutable,^M
                                      NULL);^M
 }^M
이번글에서 가장 중요한 GraphicsJNI::setJavaPixelRef를 보자
frameworks/base/core/jni/android/graphics/Graphics.cpp
augments가 ctable=NULL, reportSizeToVM = true로 넘어왔음을 기억하자
bool GraphicsJNI::setJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
                                  SkColorTable* ctable, bool reportSizeToVM) {
    Sk64 size64 = bitmap->getSize64();
    if (size64.isNeg() || !size64.is32()) {
        doThrow(env, "java/lang/IllegalArgumentException",
                     "bitmap size exceeds 32bits");
        return false;
    }

    size_t size = size64.get32();
    jlong jsize = size;  // the VM wants longs for the size
    if (reportSizeToVM) {
        //    SkDebugf("-------------- inform VM we've allocated %d bytes\n", size);
//VM에 메모리 관련 뭔가를 하는것 같은데 이번글과 긴밀하게 관련 없는것 같으니 패스
        bool r = env->CallBooleanMethod(gVMRuntime_singleton,
                                    gVMRuntime_trackExternalAllocationMethodID,
                                    jsize);
        if (GraphicsJNI::hasException(env)) {
            return false;
        }
        if (!r) {
            LOGE("VM won't let us allocate %zd bytes\n", size);
            doThrowOOME(env, "bitmap size exceeds VM budget");
            return false;
        }
    }
    // call the version of malloc that returns null on failure
//actual pixel memory에서 bitmap을 복사할 storage 공간을 잡는다.    
void* addr = sk_malloc_flags(size, 0);
    if (NULL == addr) {
        if (reportSizeToVM) {
            //        SkDebugf("-------------- inform VM we're releasing %d bytes which we couldn't allocate\n", size);
            // we didn't actually allocate it, so inform the VM
            env->CallVoidMethod(gVMRuntime_singleton,
                                 gVMRuntime_trackExternalFreeMethodID,
                                 jsize);
            if (!GraphicsJNI::hasException(env)) {
                doThrowOOME(env, "bitmap size too large for malloc");
            }
        }
        return false;
    }
//SkMallocPixel로 이미지를 가져올 storage인 addr을 감싼다. 내부적으로 어떻게 하는지는 모르겠지만 화면에 그려질 pixel memory를 addr에 복사하는것 같다. reportSizeToVM이 true이므로 AndroidPixelRef실행. 하지만 AndroidPixelRef의 구현은 VM이 정상인지(?) 체크만 하고 다시 SKMallocPixelRef를 콜한다. SkMallocPixelRef:SkPixelRef 관계이고 void* fStorage를 추가적으로 관리한다. 아무래도 내부적으로 어떻게인지는 모르겠지만 화면에 그려질 pixel memory를 fStorage에 카피하는것 같다.
    SkPixelRef* pr = reportSizeToVM ?
                        new AndroidPixelRef(env, addr, size, ctable) :
                        new SkMallocPixelRef(addr, size, ctable);
//SkPixelRef 을 최종적으로 그림을 담을 bitmap의 fPixelRef에 pr을 설정하고 fPixels에는 pr.pixels()를 통해 PixelRef의 void* fPixels를 지정한다.
    bitmap->setPixelRef(pr)->unref();
    // since we're already allocated, we lockPixels right away
    // HeapAllocator behaves this way too
//하일라이트다. actual pixel memory를 카피한다(어떻게인지는 모르겠음). SkBitmap.lockPixel() -> SkPixelRef.lockPixel() -> SkPixelRef.onLockPixels -> SkMallocPixelRef.onLockPixels 를 연쇄적으로 콜한다. SkMallocPixelRef.fStorage가 SkPixelRef.fPixels에 카피되고 그것이 다시 SkBitmap.fPixels에 카피된다.
  bitmap->lockPixels();
     return true;
 }
결국 그림을 복사하는것은 SkPixelRef가 담당한다. SkPixelRef이 이 포스트에 결론이다. 근데 mechanism을 잘 모르겠다. http://skia.googlecode.com/svn/trunk/docs/html/index.html 를 보면 

This class is the smart container for pixel memory, and is used with SkBitmap. A pixelref is installed into a bitmap, and then the bitmap can access the actual pixel memory by calling lockPixels/unlockPixels.

This class can be shared/accessed between multiple threads.

라고 설명을 한다. 즉 lockPixels이 그림을 복사해 준다는 것인데..

external/skia/src/core/SkBitmap.cpp 를 보면 과연 SkMallocPixelRef는 addr을 감싸기만 한다는것 을 알 수 있다.
SkMallocPixelRef::SkMallocPixelRef(void* storage, size_t size,
                                   SkColorTable* ctable) {
    SkASSERT(storage);
    fStorage = storage;
    fSize = size;
    fCTable = ctable;
    ctable->safeRef();
}      
가장 중요한 lockPixels를 분석하자
external/skia/src/core/SkBitmap.cpp
void SkBitmap::lockPixels() const {
    if (NULL != fPixelRef && 1 == ++fPixelLockCount) {
//SkPixelRef의 lockPixel을 콜한다.
        fPixelRef->lockPixels();
        this->updatePixelsFromRef();
    }
    SkDEBUGCODE(this->validate();)
}  

SkPixelRef의 lockPixels을 다시 부른다.
void SkPixelRef::lockPixels() { 
    SkAutoMutexAcquire  ac(*fMutex);
         
    if (1 == ++fLockCount) {
        fPixels = this->onLockPixels(&fColorTable);
    }
}   
SkPixelRef의 onLockPixels는 virtual이므로 자식인 SkMallocPixelRef의 onLockPixels를 보면
void* SkMallocPixelRef::onLockPixels(SkColorTable** ct) {
    *ct = fCTable;
    return fStorage;
}  
단순히 fStorage를 반환한다... 이럴수가 SkMallocPixelRef, SkBitmap, SkPixelRef사이에서 변수를 주고받기만 하다 끝나버렸다.  어디서 화면에 그려질 pixel memory를 가져오는지 알수가 없다. 다만 최종 소스는 fStorage임을 알 수 있을 뿐이다...


그러므로 frameworks/base/core/jni/android/graphics/Graphics.cpp 로 고고씽
변수개수가 약간 다른데 왜 그런지는 모르겠고,  결과인 bitmap을 jni를 통해 다시 Java로 돌려준다.

gBitmap_class는 android/graphics/Bitmap Class이고 gBitmap_constructorMethodID 는 생성자이다.
jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable, jbyteArray ninepatch, int density)
{
    SkASSERT(bitmap != NULL);
    SkASSERT(NULL != bitmap->pixelRef());

    jobject obj = env->AllocObject(gBitmap_class);
    if (obj) {
        env->CallVoidMethod(obj, gBitmap_constructorMethodID,
                            (jint)bitmap, isMutable, ninepatch, density);
        if (hasException(env)) {
            obj = NULL;
        }
    }
    return obj;
}
한마디로 Bitmap(new SkBitmap(bitmap), isMutable=true, null, null); 을 콜한것인데... Java로 고고씽
 private Bitmap(int nativeBitmap, boolean isMutable, byte[] ninePatchChunk,
         int density) {
     if (nativeBitmap == 0) {
         throw new RuntimeException("internal error: native bitmap is 0");
     }
     // we delete this in our finalizer
     mNativeBitmap = nativeBitmap;
     mIsMutable = isMutable;
     mNinePatchChunk = ninePatchChunk;
     if (density >= 0) {
         mDensity = density;
     }
 }
c에서 jni를 통해 넘어온 bitmap을 wrapping해준다.

출처:안드로이드에서 DataBase를 다뤄보자! (1)


SQLite 1

-> SQLite는 다른 프로그램에 임베팅하기 좋으면서도 깔끔한 SQL 인터페이스를 제공
-> 메모리도 적게 사용하면서 속도도 빠르다.
-> 실행파일과 소스 코드가 무료이고 공개되어 있기 때문에 많이 사용된다.
-> 안드로이드는 SQLite를 내장하고 있으며, 모든 안드로이드 애플리케이션은 간단하게 SQLite 데이터베이스를 생성해 활용할수 있다.
-> SQLite는 표준 SQL 인터페이스를 사용한다.
-> SQLite가 JDBC를 기본 API로 제공하지 않고, 휴대폰과 같은 환경에서 JDBC와 같은 규모 있는 프레임웍은 여러모로 무리가 된다.
-> 액티비티는 일반적으로 컨텐트 프로바이더나 서비스 등을 통해 데이터베이스에 접근한다.


SQLite 2

-> SQL 문법에 맞는 명령을 통해 데이터를 가져오거나(SELECT) 데이터를 변경하고(INSERT 등) 데이터 구조를 정의하는(CREATE TABLE 등) 작업을 처리한다.
-> 실제 데이터를 추가할 때는 컬럼마다 데이터 타입에 상관없이 어떤 데이터라도 마음대로 넣을수 있다. 예를 들어 INTEGER로 정의된 컬럼에 문자열 값도 아무런 문제없이 넣을수 있다.
-> 위와 같은 기능을 매니페스트 타입이라고 표현한다.
-> 매니페스트 타입 입장에서 보면 데이터 타입은 컬럼 자체가 아닌 개별값에 연결되는 속성이다. 따라서 SQLite는 애초에 해당 컬럼에 지정된 데이터 타입과 상관없이 어떤 데이터 타입의 어떤 값이라도 아무 컬럼에나 집어넣을수 있다.


-> 표준 SQL 구문에 정의된 기능 가운데 지원하지 않는 기능 : FOREIGN KEY, 중첩 트랜잭션, RIGHT OUTER JOIN, FULL OUTER JOIN, ALTER TABLE 등

 
기초

-> 데이터베이스를 생성하고 오픈하려면 SQLiteOpenHelper 객체를 사용한다.
-> SQLiteOpenHelper 클래스는 애플리케이션에서 요구하는 내용에 따라 데이터베이스를 생성하거나 업그레이드하는 기능을 제공한다.
-> SQLiteOpenHelper 클래스를 상속받아 구현하려면 다음과 같은 세가지 기능을 준비해야한다
-> 1. 생성 메소드 : 상위 클래스의 생성 메소드를 호출, Activity 등의 Context 인스턴스와 데이터베이스의 이름, 커서 팩토리(보통 Null 지정) 등을 지정하고, 데이터베이스 스키마 버전을 알려주는 숫자값을 넘겨 준다.
-> 2. onCreate() 메소드 : SQLiteDatabase를 넘겨 받으며, 데이블을 생성하고 초기 데이터를 추가하기에 적당한 위치이다.
-> 3. onUpgrade() 메소드 : SQLiteDatabase 인스턴스를 넘겨 받으며, 현재 스키마 버전과 최신 스키마 버전 번호도 받는다.

-> SQLiteOpenHelper를 상속받은 클래스를 사용하려면 먼저 인스턴스를 하나 생성한 다음, 하려는 작업이 읽기 전용인지 여부에 따라 getReadableDatabase()나 getWritealbleDatabese() 메소드를 호출 한다.

 

db = (new DatabaseHelper(getContext())).getWritableDatabase();
return (db == null) ? false : ture;


-> 결과적으로 db 변수에 SQLiteDatabase 인스턴스를 받아오게 되는데, SQLiteDatabase 인스턴스를 사용해 데이터를 호출하거나 내용을 변경 할수 있다.
-> 액티비티가 종료되는 등 데이터베이스를 모두 사용하고 나면 SQLiteDatabase인스턴스의 close() 메소드를 호출해 연결을 해제한다.


테이블 준비

-> 테이블과 색인 등을 생성하려면 원하는 DDL 구문을 준비해서 SQLiteDatabase 인스턴스의 execSQL() 메소드를 호출해야 한다.
 

db.execSQL("CREATE TABLE constants (_id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, value REAL);");


-> constants라는 테이블이 생성되고, constants 테이블 기본키로 _id 컬럼이 사용되며 _id 컬럼은 정수형의 숫자값이 자동으로 증가되는 컬럼이다.
-> 실제 데이터는 title이라는 문자열 컬럼과 value라는 실수형 컬럼에 들어간다.
-> 키본키에 해당하는 컬럼에 대해서는 자동으로 색인을 생성한다.
-> 다른 컬럼에도 색인이 필요하다면 CREATE INDEX 구문으로 색인을 건다.
-> 테이블이나 색인을 제거해야 하는 상황이라면 DROP INDEX나 DROP TABLE 구문을 execSQL()로 실행하면 된다.



데이터 추가

-> 데이터를 추가하는 방법은 2가지가 있다.
-> 1. execSQL() 메소드는 값을 가져오는 문장이 아닌 INSERT, UPDATE, DELETE등의 모든 SQL 구문을 처리할 수 있다.
 

db.execSQL("INSERT INTO widgets (name, inventory)" + "VALUES ('Sprocket', 5)");


-> 2. SQLiteDatabase 클래스에서 제공하는 insert(), update(), delete() 등의 메소드를 사용하는 방법이 있다. 이와같은 개별 메소드는 인자로 입력받은 값을 조합해 최종적으로 SQL 문장을 동일하게 실핼하도록 만들어져 있다.
-> 개별 메소드는 Map과 비슷한 구조로 만들어져 있으면서 SQLite의 데이터 타입에 맞춰 동작하도록 구성된 ContentValues 객체를 사용해 동작한다.
-> 지정한 키에 해당하는 값을 찾아올때 단순하게 get() 메소드를 사용하는 대신, getAsInteger(), getAsString() 등의 메소드를 호출한다.
-> insert() 메소드는 대상이 되는 테이블 이름, null 처리 컬럼명, ContentValues 객체에 컬럼별 값을 넣어 인자로 넘겨 준다.
-> SQLite는 값이 하나도 없는 행은 허용하지 않는다. 따라서 ContentValues 인스턴스 값이 하나도 없는 경우 행이 생성되지 않기 때문에 이런경우 null 처리 컬럼 이름으로 지정된 컬럼값으로 NULL을 지정해 행이 생성되게 한다.

 
contentValues cv = new ContentValues();
cv.put(ContentValues.TITLE, "Gravity, Death Star I");
cv.put(ContentValues.VALUE, SensorManager.GRAVITY_DEATH_STAR_I);
db.insert("constants", getNullColumnHack(), cv);


-> update() 메소드는 대상 테이블 이름과 변경할 값이 들어 있는 ContentValues 객체를 넘겨준다.
-> 값을 변경할 대상을 한정지으려면 WHERE 구문과 함께 WHERE 조건에 해당하는 값 역시 넘겨줌
-> WHERE 구문에 물음표로 지정된 위치에 각자의 값이 배치돼 처리된다.
-> update() 메소드는 다른 정보를 사용해 계산된 값이 아닌 고정된 값을 갖는 컬럼만 변경할 수 있으므로, 필요한 경우에는 execSQL() 메소드를 사용해야 할 수도 있다.
-> WHERE 구문에 표시된 물음표와 각 조건값을 지정하는 방법은 다른 SQL API에서 많이 사용하던 방법과 별반 다르지 않다.

 

// replacements는 ContentValues 인스턴스
String[] parms = new String[] {"snicklefritz"};
db.update("widgets", replacements, "name=?", parms); 


-> delete() 메소드 역시 테이블 이름과 WHERE 구문을 사용한다.
-> 변경할 값을 지정하지 않는다는 점만 제외하면 update()와 동일한 방법으로 동작한다.
 

데이터 불러오기

-> INSERT, UPDATE, DELETE와 비슷하게 SELECT 구문으로 데이터를 가져올때도 2가지 방법을 사용할 수 있다.
-> 1. rawQuery() 메소드를 사용해 SELECT 구문을 직접 실행
-> 2. query() 메소드를 인자로 각 부분의 값을 넘겨 실행
-> SQLiteQueryBuilder 클래스와 관련된 부분과 커서와 커서 팩토리에 관한 부분이 가장 복잡하다.

 

SQL문 직접 작성
-> API 호출 방법만 놓고 보면 rawQuery() 메소드를 사용하는 방법이 가장 간단하다.
-> rawQuery() 메소드에 SELECT 구문을 넘겨 주기만 하면 된다.
-> SELECT 구문 역시 위치에 맞는 인자 배열을 함께 넘겨 줄수 있다.
 

Cursor c = db.rawQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='constants'", null);

 

-> 위의 코드는 SQLite의 기본 테이블 가운데 하나인 sqlite_master 테이블 내용을 가져오는데, 내용으로 보면 constants라는 이름의 테이블이 만들어져 있는지 확인하는 구문이다.
-> 결과로 받아오는 값은 Cursor 인스턴스인데, Cursor를 사용하면 여러 건의 결과를 하나씩 받아오면서 처리할수 있다.
-> SELECT 문장이 동적으로 변경되지 않고 아예 프로그램 내부에 고정시켜버릴 예정이라면 위 방법이 가장 간단하다.

-> SELECT 구문 가운데 이부분이 동적으로 변경되거나, 위치로 인자값을 지정하는 방법으로 한계가 있는 수준이라면 굉장히 복잡해진다.
-> 예를 들어 값을 가져와야 할 컬럼 개수가 개발 당시에 정해지지 않고 동적으로 변경된다면 처리하기 쉽지 않다. 그렇다고 해서 컬럼 이름을 필요할 때마다 쉼표로 연결해 사용하는건 물론 좋은 방법이 아니다. 이런 경우에는 query() 메소드를 사용하는게 훨씬 간편하다.
 

일정한 형식의 쿼리

-> query() 메소드는 SELECT 구문의 각 부분을 쪼개 각 인자로 넘겨받고, 최종적으로는 SELECT문을 생성해 실행한다.
-> query() 가 받아서 처리하는 인자의 순서
-> 1. 대상 테이블 이름
-> 2. 값을 가져올 컬럼 이름의 배열
-> 3. WHERE 구문. 물음표를 사용해 인자의 위치를 지정할 수 있다.
-> 4. WHERE 구문에 들어가는 인자값
-> 5. GROUP BY 구문
-> 6. ORDER BY 구문
-> 7. HAVING 구문
-> 테이블 이름을 제외한 각 값이 필요없는 경우라면 null을 지정한다.

 

String[] columns={"ID", "inventory"};
Steing[] parms={"snicklefritz"};
Cursor result=db.query("widgets", columns, "name=?", parms, nullnullnull);
 

쿼리 구문 생성

-> SQLiteQueryBuilder 클래스를 활용하면 훨씬 다양한 방법으로 UNION이나 하위 쿼리 등을 포함하는 복잡한 구문을 생성할 수 있다.
-> SQLiteQueryBuilder 클래스가 ContentProvider 인터페이스와 완벽하게 맞아 떨어진다.
-> 컨텐트 프로바이더의 query() 메소드를 구현하는 가장 일반적인 방법은 SQLiteQueryBuilder 인스턴스를 생성하고 기본값을 일부 채워넣은 다음 전체 쿼리를 생성하고 실행할수 있게 구성한다.
-> SQLiteQueryBuilder 클래스를 사용해 요청을 처리하는 컨텐츠 프로바이더의 코드

 

@Override
public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, String sort){
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();

qb.setTables(getTableName());
 

if (isCollectionUri(url)){
    qb.setProjectionMap(getDefaultProjection());
}else{
    qb.appendWhere(getIdColumnName()+"="+url.getPathSegments().get(1));
}


String orderBy;

 

if (TextUtils.isEmpty(sort)){
    orderBy=getDefaultSortOrder();
}else{
    orderBy=sort;
}


    Cursor c = qb.query(db, projection, selection, selectionArgs, nullnull, orderBy); 
    c.setNotificationUri(getContext().getContentResolver(), url);
    return c;
}

 

-> 1. SQLiteQueryBuilder 인스턴스를 생성
-> 2. 쿼리에 사용할 테이블 이름을 설정 (setTables(getTableName())).
-> 3. 값을 가져올 기본 컬럼 이름의 목록을 지정하거나 (setProjectionMap()), 또는 Uri값에 들어 있는 ID값으로 테이블 항목 가운데 특정한 값을 가져올 수 있도록 WHERE 구문을 추가했다(appendWhere()).
-> 4. 마지막으로 기본값과 요청이 들어온 값을 조합해 생성된 쿼리 구문을 실행한다.
(qb.query(db, projection, selection, selectionArgs, null, null, orderBy))

 

-> SQLiteQueryBuilder에서 쿼리를 직접 실행하는 대신 buildQuery() 메소드를 호출해 최종 생성된 SELECT 구문만을 리턴하게 할 수도 있다. 그러면 넘겨 받은 SELECT문을 필요할때 실행할수 있다.

 

커서 활용

-> SELECT 쿼리를 어떻게 실행하건 간에 그 결과로는 Cursor 인스턴스를 받는다.
-> 커서 개념을 안드로이드와 SQLite에서 구현한 클래스가 바로 Cursor다.
-> getCount() 메소드 : 전체 결과 건수가 몇개인지 확인할 수 있다.
-> moveToFirst(), moveToNext(), isAfterLast() 등의 메소드 : 결과건을 모두 확인할수있다.
-> getColumnNames() 메소드 : 결과에 포함된 전체 컬럼 이름을 알수 있다.
-> requery() 메소드 : 쿼리를 재실행 할수 있다.
-> close() 메소드 : 커서가 확보한 자원을 모두 해제한다.
 

Cursor result = db.rawQuery("SELECT ID, namem inventory FROM widgets");
 

result.moveToFirst();
 

while (!result.isAfterLast()){
   int id = result.getInt(0);
   String name = result.getString(1);
   int inventory = result.getInt(2);
//실제 필요한 작업 처리
 

result.moveToNext();
}
result.close();
-> widgets 테이블에 있는 항목을 모두 가져와 모든 결과값을 뽑아내는 반복문 예제

 

커서 구현

-> 기본적으로 제공하는 Cursor 인스턴스 대신 Cursor를 상속받아 새로운 커서를 구현해야 할 피요가 있을 수도 있다.
-> queryWithFactory() 메소드나 rawQueryWithFactory() 메소드에 SQLiteDatabase.CursorFactory 인스턴스를 인자로 넘겨 사용한다.
-> CursorFactory 클래스는 newCursor() 메소드가 구현된 내용에 따라 새로운 Cursor를 생성한다.
-> 일반적인 안드로이드 애플리케이션을 개발하고 있다면 커서를 새로 구현해야할 일이 많지 않다.

 

데이터 직접 다루기

-> 에뮬레이터에는 sqlite3 프로그램이 포함되어 있고, adb shell 명령을 통해 실행해 사용할수있다.
-> 에뮬레이터의 셸에 접속한 다음 sqlite3명령을 실행하면서 데이터베이스 파일이 위치한 경로를 함게 지정해 주면 된다.
-> 데이터베이스 파일의 일반적인 위치 : /data/data/your.app.package/database/your-db-name
-> your.app.package 부분은 애플리케이션이 들어 있는 자바 패키지 명을 의미한다.
-> your-db-name 부분은 createDatabase() 명령을 실행할때 지정했던 데이터베이스 이름을 넣는다.
-> sqlite3 프로그램은 충분한 기능을 갖추고 있으며, 콘솔 화면에서 데이터베이스를 다루는게 익숙하다면 괜찮은 방법이다.
-> 콘솔 인터페이스보다 좀더 GUI를 갖춘 화면이 필요하다면, 위의 특정 경로에 보관되어 있는 데이터베이스 파일을 복사한 다음, 데스크탑에서 SQLite 데이터베이스 파일을 인식하는 다양한 프로그램을 활용해 데이터를 조회하고 다룰 수 있다.
-> 바깥으로 불러낸 복사본에 변경 작업을 진행했다면 변경된 데이터베이스 파일을 다시 기기에 업로드해야 반영된다.
-> 데이터베이스 파일을 기기에서 뽑아내려면 adb pull 메소드를 사용하여, 원본 경로와 대상 디렉토리 등을 적어주면 파일을 복사할수 있다.
-> 변경된 데이터베이스 파일을 기기에 업로드 하려면 adb push 명령을 사용한다.
-> adb push도 adb pull과 마찬가지로 원본 파일 경로와 대상 디렉토리 등을 알려줘야 한다.
-> 일반적으로 가장 많이 사용되는 SQLite 클라이언트 프로그램은 파이어폭스 브라우저의 확장 기능으로 구현돼있는 SQLite Manager이다. 파이어 폭스 확장 기능이기 때문에 운영체제 플랫폼에 상관없이 어디서든 사용할 수 있다.

출처 : http://blog.naver.com/oh4zzang


안드로이드 테이블 존재여부 확인하기 - Sqlite mater table query

 

 

안드로이드 sqlite에서 다음과 같이, master table 에 접근해 해당 table 존재 여부를 알 수 있다. 

다음은 SQLiteDatabase를 이용한 간단한 로그 찍어 테이블 이름을 확인하는 쿼리다~~


      Cursor c = db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'", null);
      if(c.moveToFirst()) {
       for(;;) {
        Log.e(TAG, "table name : " + c.getString(0));        
        if(!c.moveToNext())
         break;
       }
      }

 

 

    특정 테이블에 대해서 존재하는지 알기 위해서는 SELECT 문을 아래와 같이 하면 된다~


 

"SELECT name FROM sqlite_master WHERE type='table' AND name ='테이블명'

 

 

    테이블 생성 시 존재여부를 따로 쿼리해보고 테이블 생성하는 것이 귀찮게 느껴진다면,

    아래처럼 테이블 생성시 CREATE문에 "CREATE TABLE IF NOT EXISTS" 이렇게 해주면~

    테이블이 이미 존재한다면 테이블을 생성하지 않는다. >0<


String sql = "CREATE TABLE IF NOT EXISTS " + "테이블 명"+ " (_id integer primary key autoincrement, " + "column 명..)";
db.execSQL(sql);

 

logcat으로 찍어본 결과는 아래와 같다~~

밑에 2개의 테이블은 현재 프로젝트와 연관있는 이름들이라~ 모자이크 처리 ^^

존재하는 테이블 이름들을 잘 받아오는것을 볼수 있다ㅋ

 

 

 


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

질문 올리는 김에 오늘 찾아낸 팁 하나 올려봅니다.
데이터베이스에 있는 내용을 쿼리해서 리스트에 바인딩 할 때 SimpleCursorAdapter 를 사용하시는 건 다들 아시죠?
하지만 요 어댑터의 맹점은 모든 필드를 하나의 아이템 위젯에 밖에 연결할 수 없다는 것입니다.

 cs = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_2, cursor,
                                        new String[] { "capital", "country" },
                                        new int[] { android.R.id.text1, android.R.id.text2 });

이 코드는 쿼리해 받아온 cursor 에서 capital, country 필드 두개를 리스트 아이템2 기본 템플릿에 바인딩하는 코드입니다.
인터넷 어디를 뒤져봐도 Cursor 를 어댑팅하는데 요런 예제밖에 없더라구요.

제가 하려는 것은 여러개의 필드를 제 맘껏 요리 붙이고 저리 붙이고 하여 띄우는 것이었습니다.
예를 들면, capital 필드의 내용과 country 필드의 내용을 합쳐(둘다 String이므로) 일반 리스트에 표시하는 것이죠.
실제 개발 중 너무나도 흔히 있는 일일텐데 관련된 예제가 하나도 없어 삽질을 좀 했습니다.

사용법은 의외로 간단합니다.
우선 CursorAdapter 를 상속받은 커스텀 커서 어댑터 클래스를 생성합니다.
아래의 코드에선 CombiningCursorAdapter 라고 이름 붙였습니다.

public class CombiningCursorAdapter extends CursorAdapter {
    public CombiningCursorAdapter(Context cntx, Cursor cursor)
    {
        super(cntx, cursor);
    }

    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        ((TextView) view).setText(cursor.getString(0) + " / " + cursor.getString(1));
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        LayoutInflater inflater = LayoutInflater.from(context);
        View view = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
        return view.findViewById(android.R.id.text1);
    }
}

이클립스에서 클래스 추가하시면 자동으로 생성자, bindView, newView 를 오버라이드 해줍니다.
생성자는 뭐 생성자구요; 나머지 두 개 메소드 부분이 조금 특이합니다.
ArrayAdapter 의 경우는 getView 를 오버라이드 해서 사용하고, 매개변수인 convertView 의 내용의 null 값을 체크해서 할당합니다만,

@Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if( convertView == null ) {
            // view 생성
        }
        // bind
        return super.getView(position, convertView, parent);
    }

CursorAdapter 의 경우는 뷰의 생성과 바인드가 분리되어 있다고 보시면 되겠습니다.
생성은 newView 메소드, 바인드는 bindView 메소드지요.

아래는 뷰를 생성하는 newView 코드입니다.

@Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        LayoutInflater inflater = LayoutInflater.from(context);
        View view = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
        return view.findViewById(android.R.id.text1);
    }

이 부분이 결정적인 삽질을 하게 된 부분입니다ㅠ
일단 어떤 뷰든지 리턴하게 되면 그 리턴한 뷰를 매 아이템마다 띄워주게 되어 있습니다.
이런 복잡한 코드 대신 return new TextView(context); 이렇게 사용해도 동작은 하지요.
하지만 안드로이드에서 기본 제공하는 android.R.id.xxx 류의 기본 아이템 위젯을 사용하고 싶어 이리저리 찾아봤는데 관련 예제가 도무지 없더군요;
온갖 시도 끝에 기본 제공하는 레이아웃 android.R.layout.simple_list_item_1 을 inflater 를 이용해 inflate 해서 findViewById 를 해보니 됩니다.
어째 뷰 리소스 아이디를 이용해 뷰를 생성하는 메소드는 없는걸까요 -_-
아무튼 이 부분이 어댑팅하여 띄워줄 리스트의 아이템 뷰를 생성하는 부분입니다.

생성을 했으니 이제 바인드를 해야겠지요.

@Override
    public void bindView(View view, Context context, Cursor cursor) {
        ((TextView) view).setText(cursor.getString(0) + " / " + cursor.getString(1));
    }

간단합니다. 여기서 view 에는 아까 newView 에서 리턴한 녀석이 날아옵니다.
TextView 로 캐스트해서 setText 날려주시면 되지요.
알아서 해당 위치로 포지션이 셋팅된 cursor 가 함께 날아오므로, 포지션 걱정 없이 cursor 를 마구 갖다 써주시면 되겠습니다.


생각보다는 꽤 간단한 방법이죠?
굉장히 여러가지 방법으로 커스터마이징이 가능할 것 같습니다.
위의 예제처럼 여러 필드를 합쳐 하나로 띄운다거나,
데이터베이스에 이미지URL 이 있는 경우 ImageView 를 이용해 바로 리스트에 붙인다거나 하는 작업이 가능할 듯 합니다.

ArrayAdapter 에 관련해서는 예제가 굉장히 많은데, 실 개발시 오히려 사용빈도가 높은 CursorAdapter 의 경우엔 커스텀 어댑터 예제가 보이지 않아 한번 두드려 보았습니다.
도움되셨길 바랍니다
그냥 Base64 자바파일을 src에 패키지 이름 추가하고 사용하면 된다..18181818..ㅋㅋ


The official documentation states that base64 encode and decode in Android is in the Android.util package. But when you go to use it you will find it's not there!

After much searching, I confirmation Android left base64 encode and decode out of the Android.util package. So what to do? Don't worry, Robert W. Harder has but together a very fast Base64 encoding and decoding class you can include in your project. Here is how to add base64 encode and decode to your project.

Step 1. Visit Robert's website http://iharder.sourceforge.net/current/index.html and download the class file.

 
Step 2. The zip file you downloaded in step 1 includes the base64 class and a folder with examples and documentation.
Step 3. Copy the Base64.java class file into your project. Copy into your appropriate "src" folder.
Step 4. Refresh your project so the new file is included.
Step 5.When the Base64.java file is included in your project, you will get the following error "The declared package does not match the expected package". Fix this by opening the class file and adding a package declaration that matches your project.
Step 6. Done. You're ready to base64 encode and decode.
 
출처 : 안드로이드 펍(http://www.androidpub.com/837593)

안녕하세요.
개발자 질문란에 답변을 달았더니.. 쪽지를 주셔서 조금 더 자세히 말씀드리기 위해서 개발자 정보 공간에 글을 써봅니다.

기본적으로 카메라를 찍는 Activity를 직접 만들기 위해서는.. SurfaceView등을 이용하여 구현하여야 하지만,
이 글의 목적은 어떻게해서든 사진을 찍은 '후'에, 그 정보를 받아와 ImageView에 뿌리거나, Bitmap으로 받는 것이므로
그쪽에 초점을 두겠습니다.
버전은 2.1을 기준으로 합니다.


우선 Intent를 이용하여 내장 카메라를 불러옵니다.
저는 사진 찍기 버튼 클릭시 동작하게 해놓았으므로 OnClickListener에 구현되어있습니다.

1.private OnClickListener photoPickClick = new OnClickListener() {
2.   public void onClick(View v) {
3.       // TODO Auto-generated method stub
4.       Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
5.       startActivityForResult(cameraIntent,1);
6.       }
7.   };

그 후에, 
onActivityResult를 통하여 받아옵니다.

01.protected void onActivityResult(int requestCode, int resultCode, Intent data) {
02.    // TODO Auto-generated method stub
03.    super.onActivityResult(requestCode, resultCode, data);
04.    if(resultCode!=0){
05.        if(requestCode==1&&!data.equals(null)){
06.                try{
07.                profileBitmap = (Bitmap)data.getExtras().get("data");
08.                profileView.setImageBitmap(profileBitmap);
09.                profileView.setScaleType(ImageView.ScaleType.FIT_XY);
10.                } catch(Exception e){   
11.                    return;
12.                }
13.            }
14.        }
15.    }

당연히 profileBitmap에는 비트맵으로 사진을 받아옵니다.
저는 보통 DB에 Bitmap을 저장할때는.. Base64로 String 변환을 시켜서 합니다
일단 변환해서 DB에 저장을 하고, 나중에 단말에서 받아올때는 당연히 decode도 해줘야하구요~

일단 encode하는 부분만 기술해놓겠습니다.

1.ByteArrayOutputStream stream = new ByteArrayOutputStream();
2.profileBM.compress(CompressFormat.PNG, 100, stream);
3.byte[] image = stream.toByteArray();
4.String profileImageBase64 = Base64.encodeBytes(image);

다만, 위에서 받아온 사진의 경우는 원본 이미지가 아니고 썸네일정도 수준의 크기입니다.

만약 더 큰 사진을 원하시면, 사진을 찍은 후에 일단 저장하고 불러오시거나
Intent를 만들때, EXTRA_OUT를 줘서 URI에 담아오는 방법이 있는데..
이 방법은 단말마다 멋대로 동작한다는 이야기가 있습니다 -_-;
(샘플링을 4로해서 1/4품질로 넘어오기때문에 단말마다 해상도가 자기 멋대로..)

01.private OnClickListener photoPickClick = new OnClickListener() {
02.  public void onClick(View v) {
03.      // TODO Auto-generated method stub
04.      Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
05.      File file = new File(Environment.getExternalStorageDirectory(), "picture.jpg"
06.      cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
07.      startActivityForResult(cameraIntent,1);
08.      }
09.  };

위에서 getExternalStorageDirectory()로 받아왔는데, SD카드 내에 절대 경로로 지정해주셔도 상관없구요.

onActivityResult에서 profileBitmap = (Bitmap)data.getExtras().get(MediaStore.EXTRA_OUTPUT);
로 받아오시면 되겠습니다.

출처: 안드로이드펍 

안드로이드 개발 참고 사이트 모음입니다. 

AndroidPub : 안드로이드펍 한국 안드로이드 사용자 및 개발자 커뮤니티. 
  소개       : 한국 안드로이드 커뮤니티
  홈페이지 : http://www.androidpub.com

공식 안드로이드 공식 홈페이지들
  소개글        : 안드로이드의 전체 소스를 다운받을 수 있음
  홈페이지     : 소스 http://source.android.com
                     공식 http://www.android.com
                     마켓 http://market.android.com
                     개발자 http://developer.android.com

- 안드로이드 써드파티 마켓 사이트 모음
  소개글        : 안드로이드의 다양한 써드파티 마켓 정보 모음
  홈페이지     : http://www.androidpub.com/22520


Android Code Snippets
  소개       : 간단한 안드로이드 코드 모음
  홈페이지 : http://www.androidsnippets.org

StackOverflow Android 
  소개       : 안드로이드 질문과 답 (영문)

구글의 안드로이드 개발자들이 만든 애플리케이션 모음 (Apps for Android )
  소개        : 주로 구글의 실제 안드로이드 개발자들이 만든 샘플 애플리케이션들로 안드로이드의 구조를 잘 이해하고
                  작성한 애플리케이션들이라서 주옥같은 예제가 많이 들어있음. PhotoStream는 웹서버와의 통신에서
                  참고하기 좋은 애플리케이션.

안드로이드 플랫폼 스터디 모임
  소개       : 안드로이드 애플리케이션 프로그래밍을 공부하는 것이 아니라, 안드로이드 플랫폼의 자체 이해를 목적으로 합 
                       니다. 이를 통해 안드로이드 프레임워크의 설계 및 동작 원리를 이해하고, 나아가 안드로이드의 내부 구조를 파악 
                      함으로써 효율적인 안드로이드 프로그램의 설계 및 구현 할 수 있는 지식을 쌓는 것이 저희 스터디의 목표입니다.  
  홈페이지 : http://andstudy.springnote.com/

Eyes Free TTS 텍스트 음성 변환 라이브러리
  소개       : 안드로이드 애플리케이션에서 공유해서 사용할 수 있는 TTS 라이브러리 (안드로이드 1.6에 기본 탑재될 것으로 
                 알려짐)

-  SMALI Dex assembler/disassembler
  소개       : JF 가 작업중인 Dex 어셈블러/디스어셈블러

The open mob for android
  소개       : 안드로이드 개발 관련 위키
  홈페이지 : http://wiki.andmob.org/

안드로이드 스크립트 환경 (ASE : Android Scripting Environment)
  소개       : http://www.androidpub.com/11518

Live CD for Android
  소개       : PC에서 안드로이드를 구동시키자? x86 으로 컴파일된 안드로이드 이미지를 CD에 구워서 PC에서 구동.

HTC 안드로이드 개발자 폰 지원 사이트. 
  소개       : 최신 시스템 이미지등을 다운받을 수 있음

MotoDev 모토로라 개발자 사이트
  소개       : 모토로라의 개발자 사이트 최근 안드로이드를 중심으로 구성되고 있음. PodCast등 쓸만한 정보가 제법 올라옴
  홈페이지 : http://developer.motorola.com/

차이나모바일 OMS 개발자 사이트
  소개       : 차이나 모바일 OMS SDK 정체를 드러내다. 
  홈페이지  : http://www.ophonesdn.com 

일본 안드로이드회 기술자료
  소개         : 일본의 안드로이드 개발자 협회의 기술자료 다양한 자료가 잘 정리되어있다.

본 글은 퍼가셔도 좋으나 안드로이드펍 출처를 정확히 명기해주시기 바랍니다. :) 종종 업데이트 하도록 하겠습니다. 쓸만한 링크가 있으면 댓글로 알려주세요 추가시켜놓도록 하겠습니다.

ListDataEvent.CONTENTS_CHANGED

/*Type: Interval Added
, Index0: 0
, Index1: 0
[First, a, b, c, d]
Type: Interval Added
, Index0: 5
, Index1: 5
[First, a, b, c, d, Last]
Type: Interval Added
, Index0: 3
, Index1: 3
[First, a, b, Middle, c, d, Last]
Type: Contents Changed
, Index0: 0
, Index1: 0
[New First, a, b, Middle, c, d, Last]
Type: Contents Changed
, Index0: 6
, Index1: 6
[New First, a, b, Middle, c, d, New Last]
Type: Interval Added
, Index0: 7
, Index1: 7
[New First, a, b, Middle, c, d, New Last, a]
Type: Interval Added
, Index0: 8
, Index1: 8
[New First, a, b, Middle, c, d, New Last, a, b]
Type: Interval Added
, Index0: 9
, Index1: 9
[New First, a, b, Middle, c, d, New Last, a, b, c]
Type: Interval Added
, Index0: 10
, Index1: 10
[New First, a, b, Middle, c, d, New Last, a, b, c, d]
Type: Interval Removed
, Index0: 0
, Index1: 10
[]

 * */
import java.awt.BorderLayout;

import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;

public class MainClass {
  static String labels[] "a""b""c""d" };

  public static void main(String args[]) {
    JFrame frame = new JFrame("Modifying Model");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    final DefaultListModel model = new DefaultListModel();
    for (int i = 0, n = labels.length; i < n; i++) {
      model.addElement(labels[i]);
    }
    JList jlist = new JList(model);
    JScrollPane scrollPane1 = new JScrollPane(jlist);
    frame.add(scrollPane1, BorderLayout.WEST);

    ListDataListener listDataListener = new ListDataListener() {
      public void contentsChanged(ListDataEvent listDataEvent) {
        appendEvent(listDataEvent);
      }
      public void intervalAdded(ListDataEvent listDataEvent) {
        appendEvent(listDataEvent);
      }
      public void intervalRemoved(ListDataEvent listDataEvent) {
        appendEvent(listDataEvent);
      }
      private void appendEvent(ListDataEvent listDataEvent) {
        switch (listDataEvent.getType()) {
        case ListDataEvent.CONTENTS_CHANGED:
          System.out.println("Type: Contents Changed");
          break;
        case ListDataEvent.INTERVAL_ADDED:
          System.out.println("Type: Interval Added");
          break;
        case ListDataEvent.INTERVAL_REMOVED:
          System.out.println("Type: Interval Removed");
          break;
        }
        System.out.println(", Index0: " + listDataEvent.getIndex0());
        System.out.println(", Index1: " + listDataEvent.getIndex1());
        DefaultListModel theModel = (DefaultListModellistDataEvent.getSource();
        System.out.println(theModel);
      }
    };

    model.addListDataListener(listDataListener);

    model.add(0"First");
    model.addElement("Last");
    int size = model.getSize();
    model.insertElementAt("Middle", size / 2);
    size = model.getSize();
    if (size != 0)
      model.set(0"New First");
    size = model.getSize();
    if (size != 0)
      model.setElementAt("New Last", size - 1);
    for (int i = 0, n = labels.length; i < n; i++) {
      model.addElement(labels[i]);
    }
    model.clear();
    size = model.getSize();
    if (size != 0)
      model.remove(0);

    model.removeAllElements();
    model.removeElement("Last");
    size = model.getSize();
    if (size != 0)
      model.removeElementAt(size / 2);
    size = model.getSize();
    if (size != 0)
      model.removeRange(0, size / 2);
    frame.setSize(640300);
    frame.setVisible(true);
  }
}


사용법 : File file = new FileRenamePolicy().rename(new File(원하는 파일명));

==============================================================================
import java.io.File;
import java.io.IOException;

public class FileRenamePolicy {
 
  public File rename(File f) {  //File f는 원본 파일
    if (createNewFile(f)) return f; //생성된 f가
   
    //확장자가 없는 파일 일때 처리
    String name = f.getName();
    String body = null;
    String ext = null;

    int dot = name.lastIndexOf(".");
    if (dot != -1) { //확장자가 없을때
      body = name.substring(0, dot);
      ext = name.substring(dot);
    } else {   //확장자가 있을때
      body = name;
      ext = "";
    }

    int count = 0;
    //중복된 파일이 있을때
    while (!createNewFile(f) && count < 9999) {  
      count++;
      String newName = body + count + ext;
      f = new File(f.getParent(), newName);
    }
    return f;
  }

  private boolean createNewFile(File f) { 
    try {
      return f.createNewFile();  //존재하는 파일이 아니면
    }catch (IOException ignored) {
      return false;
    }
  }
}

Introduction to Object Serialization

Java object serialization is used to persist Java objects to a file, database, network, process or any other system. Serialization flattens objects into an ordered, or serialized stream of bytes. The ordered stream of bytes can then be read at a later time, or in another environment, to recreate the original objects.

Java serialization does not cannot occur for transient or static fields. Marking the field transient prevents the state from being written to the stream and from being restored during deserialization. Java provides classes to support writing objects to streams and restoring objects from streams. Only objects that support the java.io.Serializable interface or the java.io.Externalizable interface can be written to streams.
public interface Serializable

  • The Serializable interface has no methods or fields. (Marker Interface)
  • Only objects of classes that implement java.io.Serializable interface can be serialized or deserialized

Transient Fields and Java Serialization

The transient keyword is a modifier applied to instance variables in a class. It specifies that the variable is not part of the persistent state of the object and thus never saved during serialization.

You can use the transient keyword to describe temporary variables, or variables that contain local information,


such as a process ID or a time lapse.

Input and Output Object Streams

ObjectOutputStream is the primary output stream class that implements the ObjectOutput interface for serializing objects. ObjectInputStream is the primary input stream class that implements the ObjectInput interface for deserializing objects.

These high-level streams are each chained to a low-level stream, such as FileInputStream or FileOutputStream.
The low-level streams handle the bytes of data. The writeObject method saves the state of the class by writingthe individual fields to the ObjectOutputStream. The readObject method is used to deserialize the object from
the object input stream.

Case 1: Below is an example that demonstrates object Serialization into a File

PersonDetails is the bean class that implements the Serializable interface

import java.io.Serializable;
public class PersonDetails implements Serializable {

	private String name;
	private int age;
	private String sex;
	public PersonDetails(String name, int age, String sex) {
		this.name = name;
		this.age = age;
		this.sex = sex;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
}

GetPersonDetails is the class that is used to Deserialize object from the File (person.txt).

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.List;
public class GetPersonDetails {

	public static void main(String[] args) {
		String filename = "person.txt";
		List pDetails = null;
		FileInputStream fis = null;
		ObjectInputStream in = null;
		try {
			fis = new FileInputStream(filename);
			in = new ObjectInputStream(fis);
			pDetails = (ArrayList) in.readObject();
			in.close();
		} catch (IOException ex) {
			ex.printStackTrace();
		} catch (ClassNotFoundException ex) {
			ex.printStackTrace();
		}
		// print out the size
		System.out.println("Person Details Size: " + pDetails.size());
		System.out.println();
	}
}

PersonPersist is the class that is used to serialize object into the File (person.txt).

public class PersonPersist {

	public static void main(String[] args) {
		String filename = "person.txt";
		PersonDetails person1 = new PersonDetails("hemanth", 10, "Male");
		PersonDetails person2 = new PersonDetails("bob", 12, "Male");
		PersonDetails person3 = new PersonDetails("Richa", 10, "Female");
		List list = new ArrayList();
		list.add(person1);
		list.add(person2);
		list.add(person3);
		FileOutputStream fos = null;
		ObjectOutputStream out = null;
		try {
			fos = new FileOutputStream(filename);
			out = new ObjectOutputStream(fos);
			out.writeObject(list);
			out.close();
			System.out.println("Object Persisted");
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}
}

——————————————————————————–

Case 2: Below is an example that demonstrates object Serialization into the database

PersonDetails remains the same as shown above

GetPersonDetails remains the same as shown above

Create SerialTest Table

create table SerialTest(
name BLOB,
viewname VARCHAR2(30)
);

PersonPersist is the class that is used to serialize object into the into the Database Table SerialTest.

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class PersonPersist {

	static String userid = "scott", password = "tiger";
	static String url = "jdbc:odbc:bob";
	static int count = 0;
	static Connection con = null;
	public static void main(String[] args) {
		Connection con = getOracleJDBCConnection();
		PersonDetails person1 = new PersonDetails("hemanth", 10, "Male");
		PersonDetails person2 = new PersonDetails("bob", 12, "Male");
		PersonDetails person3 = new PersonDetails("Richa", 10, "Female");
		PreparedStatement ps;
		try {
			ps = con
					.prepareStatement("INSERT INTO SerialTest VALUES (?, ?)");
			write(person1, ps);
			ps.execute();
			write(person2, ps);
			ps.execute();
			write(person3, ps);
			ps.execute();
			ps.close();
			Statement st = con.createStatement();
			ResultSet rs = st.executeQuery("SELECT * FROM SerialTest");
			while (rs.next()) {
				Object obj = read(rs, "Name");
				PersonDetails p = (PersonDetails) obj;
				System.out.println(p.getName() + "\t" + p.getAge() + "\t"
						+ p.getSex());
			}
			rs.close();
			st.close();
		} catch (Exception e) {
		}
	}
	public static void write(Object obj, PreparedStatement ps)
			throws SQLException, IOException {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ObjectOutputStream oout = new ObjectOutputStream(baos);
		oout.writeObject(obj);
		oout.close();
		ps.setBytes(1, baos.toByteArray());
		ps.setInt(2, ++count);
	}
	public static Object read(ResultSet rs, String column)
			throws SQLException, IOException, ClassNotFoundException {
		byte[] buf = rs.getBytes(column);
		if (buf != null) {
			ObjectInputStream objectIn = new ObjectInputStream(
					new ByteArrayInputStream(buf));
			return objectIn.readObject();
		}
		return null;
	}
	public static Connection getOracleJDBCConnection() {
		try {
			Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
		} catch (java.lang.ClassNotFoundException e) {
			System.err.print("ClassNotFoundException: ");
			System.err.println(e.getMessage());
		}
		try {
			con = DriverManager.getConnection(url, userid, password);
		} catch (SQLException ex) {
			System.err.println("SQLException: " + ex.getMessage());
		}
		return con;
	}
}

——————————————————————————–

Case 3: Below is an example that demonstrates object Serialization into the database using Base 64 Encoder

PersonDetails remains the same as shown above

GetPersonDetails remains the same as shown above

Create SerialTest Table

create table SerialTest(
name BLOB,
viewname VARCHAR2(30)
);

PersonPersist is the class that is used to serialize object into the Database Table SerialTest

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class PersonPersist {

	static String userid = "scott", password = "tiger";
	static String url = "jdbc:odbc:bob";
	static int count = 0;
	static Connection con = null;
	static String s;
	public static void main(String[] args) {
		Connection con = getOracleJDBCConnection();
		PersonDetails person1 = new PersonDetails("hemanth", 10, "Male");
		PersonDetails person2 = new PersonDetails("bob", 12, "Male");
		PersonDetails person3 = new PersonDetails("Richa", 10, "Female");
		PreparedStatement ps;
		try {
			ps = con
					.prepareStatement("INSERT INTO SerialTest VALUES (?, ?)");
			write(person1, ps);
			ps.execute();
			write(person2, ps);
			ps.execute();
			write(person3, ps);
			ps.execute();
			ps.close();
			Statement st = con.createStatement();
			ResultSet rs = st.executeQuery("SELECT * FROM SerialTest");
			while (rs.next()) {
				Object obj = read(rs, "Name");
				PersonDetails p = (PersonDetails) obj;
				System.out.println(p.getName() + "\t" + p.getAge() + "\t"
						+ p.getSex());
			}
			rs.close();
			st.close();
		} catch (Exception e) {
		}
	}
	public static void write(Object obj, PreparedStatement ps)
			throws SQLException, IOException {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ObjectOutputStream oout = new ObjectOutputStream(baos);
		oout.writeObject(obj);
		oout.close();
		byte[] buf = baos.toByteArray();
		s = new sun.misc.BASE64Encoder().encode(buf);
		ps.setString(1, s);
		// ps.setBytes(1, Base64.byteArrayToBase64(baos.toByteArray()));
		ps.setBytes(1, baos.toByteArray());
		ps.setInt(2, ++count);
	}
	public static Object read(ResultSet rs, String column)
			throws SQLException, IOException, ClassNotFoundException {
		byte[] buf = new sun.misc.BASE64Decoder().decodeBuffer(s);
		// byte[] buf = Base64.base64ToByteArray(new
		// String(rs.getBytes(column)));
		if (buf != null) {
			ObjectInputStream objectIn = new ObjectInputStream(
					new ByteArrayInputStream(buf));
			Object obj = objectIn.readObject(); // Contains the object
			PersonDetails p = (PersonDetails) obj;
			System.out.println(p.getName() + "\t" + p.getAge() + "\t"
					+ p.getSex());
		}
		return null;
	}
	public static Connection getOracleJDBCConnection() {
		try {
			Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
		} catch (java.lang.ClassNotFoundException e) {
			System.err.print("ClassNotFoundException: ");
			System.err.println(e.getMessage());
		}
		try {
			con = DriverManager.getConnection(url, userid, password);
		} catch (SQLException ex) {
			System.err.println("SQLException: " + ex.getMessage());
		}
		return con;
	}
}
Below is a program that shows the serialization of a JButton object to a file and a Byte Array Stream. As before theobject to be serialized must implement the Serializable interface.

PersonDetails is the bean class that implements the Serializable interface

import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

public class ObjectSerializationExample {

	public static void main(String args[]) {
		try {
			Object object = new javax.swing.JButton("Submit");
			// Serialize to a file namely "filename.dat"
			ObjectOutput out = new ObjectOutputStream(
					new FileOutputStream("filename.dat"));
			out.writeObject(object);
			out.close();
			// Serialize to a byte array
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			out = new ObjectOutputStream(bos);
			out.writeObject(object);
			out.close();
			// Get the bytes of the serialized object
			byte[] buf = bos.toByteArray();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

 

요즘 며칠동안 공부중인 캔버스 프리드로우 버전입니다영..^^

1단계 : 그냥 캔버스에 간단한 마우스 이벤트를 통한 프리드로우입니다.

2단계 : 1단계를 해보니 할 때마다 다시 페인트를 해서 번쩍번쩍 거림..쓰레드 구현 필요(우리 수업도 빨리 쓰레드,Db,IO가 나가야 할텐데..벌써 한달이 지났엉..==;)

3단계 : 이걸 DB든 파일이든 직렬화 저장해서 정말 스마트폰에 있는 그림메모처럼 만드는 게 목표.

최종단계 : 이걸 그대로 안드로이드폰에 옮겨봄...ㅋ

 

암튼 현재 1단계 성공..

 

소스첨부하니 참고하실 분 보세요.


import java.awt.BasicStroke;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.ArrayList;

import javax.swing.JFrame;


public class DrawTest extends JFrame {
 MyCanvas canvas;
 ArrayList<Vortex> list;
 public DrawTest(){
  super("DrawTest");
  
  System.out.println("DrawTest 시작");
  
  list = new ArrayList<Vortex>();
  
  canvas = new MyCanvas();
  
  canvas.setBackground(Color.WHITE);
  
  canvas.addMouseListener(new MouseAdapter(){
   public void mousePressed(MouseEvent me){
    
    list.add(new Vortex(me.getX(),me.getY(),false));
    
    
   }   
  });
  canvas.addMouseMotionListener(new MouseMotionAdapter(){
   public void mouseDragged(MouseEvent me){

    list.add(new Vortex(me.getX(),me.getY(),true));

    canvas.repaint();

   }
  });
  this.add(canvas);
  
  this.setSize(300,300);
 
 }
 
 class Vortex{
  float x;
  float y;
  boolean isDraw;
  public Vortex(float x,float y,boolean isDraw){
   this.x = x;
   this.y = y;
   this.isDraw = isDraw;   
  }
 }
 class MyCanvas extends Canvas{
  
  public void paint(Graphics g){
   
   Graphics2D g2d = (Graphics2D)g;

         g2d.setStroke(new BasicStroke(2));    // 선 굵기 설정

   int size = list.size();
   
   for(int i=0;i<size;i++){
    
    Vortex tex = (Vortex)list.get(i);
    
    if(tex.isDraw){
     
     g.drawLine((int)list.get(i-1).x, (int)list.get(i-1).y,
       (int)list.get(i).x, (int)list.get(i).y);
    }
   }
   
  }
 }
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  new DrawTest().setVisible(true);
 }

}

음..

 

 업캐스팅이 처음 들어 보는 단어라고 그러셨는데..

 

 상속을 알고 있다면 그리고 인터페이스 와 추상 클래스, 매서드 오버라이딩의 개념을

 알고 있다면 이미 업캐스팅을 사용하는 겁니다.

 

 아니 이미 업캐스팅은 문장에 만들어져 있습니다

 

 모든 자바는 Objdec 클래스에서 상속받습니다.

 명시적으

 

    class A {

 

 

} 라는게 있다면

 이미 묵시적으로

 

  class A extends Object 가 형성되는 거죠.

 

자 그럼 업태스팅은 무엇인지 알아보죠.

 

 대표적인 구문을 하나 보여드리겠습니다.

 

 

 class A{

 public void draw(){

  System.out.println(" A번입니다");

 }

 }

 

 class B extends A{

 public void draw(){

 System.out.println("B클래스입니다.");

}

}

 

class C extends B{

  public void draw(){

 System.out.println("C클래스입니다");

 }

  public void set(){

  System.out.pirnln("set매서드입니다");

}

 

 

실행부

 

 main(String[] args){

 A  a=new A();

 a.draw();

 

 // 이경우는  A클래스의 A 클래스입니다. 출력

 

 B b=new B();

 b.draw();

 

// B클래스입니다. 출력

 

 C c=new C();

 c.draw();

 c.set(); 

 

 //C클래스의 C클래스입니다. set 매서드입니다. 출력

 

 A ab=new C();

 c.draw();

 c.set();

 

 이경우는 C클래스입니다. 출력 c.set()는 에러.

 

 

 자 마지막을 보죠.

 A  ab=new C();

 라는 것을 만들었죠.

 A클래스는 C클래스로 상속이 되 있습니다.

 그런데 이 클래스를 풀이해보면

 A클래스 형을 가지는 ab는 C클래스의 메모리를 사용한다. 라는 뜻이됩니다.

 

 여기서 매서드 오버라이드 개념이 사용됐죠. draw()는 오버라이드 됐습니다.

 오버라이드가 되면 재정의를 통해 아들의 매서드를 사용합니다. 이미 알고 계시니 오버라이드는

 생략하죠.

 이상하게 c.set()은 에러가 나왔습니다. c클래스이 메모리를 사용하면서 왜 C 클래스의 매서들르

 사용 못할까요?

 

 수행순서를 생각해 보죠.

 A ab=new C(); 가 만들어지면

 상속 받은 클래스 A 에 대해 재정의 된 매서드가 있는지 검사합니다.

 draw()는 재정의가 됐으니 당연히 사용가능합니다.

 하지만 set() 은 재정의가 이루어지지 않았죠. 당연히 사용 불능입니다.

 

 

 하지만 이것이 무얼 의미하는지 진정한 의미를 파악하는게 중요합니다.

 인터페이스와 추상클래스 개념에서 업캐스팅은 무하한 위력을 발휘죠.

 또한 우리가 쉽게 사용하는 구문들 중에서 캐스팅 관련에서 업캐스팅은 많이 일어납니다.

 무심코 사용하는 구문들 대부분이 업 캐스팅의 개념을 포함하고 있습니다.

 

 실용적인 예로 인터페이스의 리스너의 들면

 마우스 리스러는 마우스의 조작을 처리합니다.

 우리는 리스터를 통해 마우스의 여러가지 특성을 부여하면

 

 자바 vm은 알아서 마우스를 제어하는 컴퓨터의 드라이버를 찾아 그 실행에 맞게

 마우스를 조작하는 역활을 해주죠. 우선 이 의미를 파악하고 리스너를 사용하십시오

 

모든 리스너는 다 이런 식입니다.

 마우스가 컴퓨터의 윈도우 환경에서 동작하려면 드라이버를 깔아야죠? ps/2나 usb드라이버를

 찾지 않습니까? 하지만 자바는 vm기반 언어기에 일일이 드라이버에 대한 정의를 해쥐 않아도 됩니다.

 리스너라는 편리한 도구가 있고, 그 리스너에서 제공하는 메서드를 사용해  마우스의 조작을

 해주는 겁니다.

 키보드 리스너의 경우도 마찬가지입니다. 이것 오에 리스너는 상당 종류가 있습니다면

 그 원리를 이해한다면 이미 반은 이해한 겁니다.

 

 

 

 그런데 아시다시피 인터페이스도 클래스의 한 종류 입니다.

 하지만 인터페이스는 껍데기입니다. 메모리를 가지지 않습니다. 메모리가 없기에 구현을 통해

 사용하는 것이죠. (메모리가 없다는 것은 그것을 동작한 자료를 넣을 공간이 없다는 겁니다.)

 

 

 마우스 리스너의 형을 가지고

 A 라는 클래스를 만들면 (A 클래스는 마우스 리스너를 구현했고, 메모리도 가지고 있게 되겠죠.)

 

 우리는 A  라는 클래스에서 리스너의 메서드를 모두 적어줘야 합니다. (그걸 방지하는게 아답터죠.) 여기서 일단 매서드 오버라이딩이 일어나고 (오버라이딩이 일어나지 않으면 구현받은 인터페이스에 접근하는게 불가능하니까요.)

 

 우리는 A클래스를 가지고 인터페이스 마우스 리스너의 제어담당 매서드에 접근해 그것을

 사용하게 해주는 겁니다.

 왜 그럴까요?

 

 인터페이스는 클래스이지만 메모리가 없습니다.

 그래소 implemets를 통해 구현하죠.

 느낌이 오십니까? (이런것이 여러가지 제작툴에서 사용되는 업캐스팅의 예입니다. 사실... 업캐스팅이 진짜 의미는 이정도를 넘어섭니다. 엄청난 아주 대단한 위력이 있죠. 남의 소스를 고치거나

 클래스를 고치고 할때도 업캐스팅은 강력합니다.)

 

 제 설명이 부족하다면 책을 찾아 보시길 바랍니다.

 아주 자세히 설명된 책도 많습니다.

 

 

 

 추상클래스의 경우도 마찬가지입니다.  추상 클래스는 클래스이지만 껍데기 입니다.

 상속 받아 클래스 안에 메모리를 만들어 줘야 사용가능하죠.

 

 업캐스팅을 처음 들어보셨다면... 책을 좀 더 보야 될것 같습니다.

 아주중요한 개념이고 이 개념을 이해해야 뒤에가서도 막히지 않습니다.

 상속 과 캐스팅이 자바의 전부라 해도 과헌이 아닙니다.

 자바는 기초가 아주 중요합니다. 지금도 계속 책을 찾아 보고 있죠.

 

 사실 그림 맞추기 게임에서 진짜 알야될 부분은 이런 기초적인 것에 대한 확실한 이해를 다지는 겁니다.

 이것 외에 배열에 대해서도 상당히 많으 부분을 알야 될겁니다.

 애플릿은 단지 툴입니다. 솔직히 제 생각에 에플릿은 그리 대단치 않습니다. 차라리 플래쉬가

 백배 낫죠.  하지만 애플릿은 그 자체로도 상당히 복잡합니다.

 버튼 하나를 넣기 위해서 여러가지 틀을 생각해서 순서에 맞게 구현해야 되지 않나요?

 그걸 외우는건 나중일입니다. 일단 기초적인 공부가 받쳐주면 왜 그런 순서로 작성해야 되는지가

 자연스럽게 들어오고 해당 클래스와 매서드들이 자연스럽게 머리에 들어오게 됩니다.

 

 

 

 

 한가지 더 예를 든다면

 무심코 사용하는 super에 대한 것을 들죠

 슈퍼는 아버지 클래스의 생성자를 참조하는 방법입니다.

 하지만 이게 어떤 의미인지 왜 그런지에 대해서는 자세히 알지 못하면 사용하는 의미가 없습니다.

 

 부모 클래스를 상속 받은 아들 클래스는 부모 클래스의 모든 기능을 가집니다.

 오버라이딩을 통해서, 업캐스팅을 통해서 모든 기능을 다 구현할 수 있죠.

 그런데 여기서 생성자를 생각해 봅시다.

 

 부모도 생성자가 있겠고, 아들도 생성자가 있겠죠. 표현하지 않더라도 디폴트 생성자라는 것을 무조건적으로 가지니까요.

 

 상속시 매소드처럼 오버라이드되어 부모 클래스의 생성자가 호출되지 않는다면 어떨까요?

 그렇다면 부모 클래스의 초기화 작업을 아들 클래스에서 모두 해줘야겠죠.

 이런 불편함이 있어서 super가 존재합니다.

 하지만 super의 명확한 의미는 이게 다가 아닙니다.

 

 상속시 생성자의 순서를 살펴보죠.

 일단 아버지 의 디폴트(매개변수가 없는 생성자)가 생성되고

 그다음 아들의 생성자가 생성됩니다.

 

 아버의 생성자는 무조건적으로 생성되지만, 오직 디폴트 생성자만 가능합니다.

 만약 매개 변수를 가지고 아버지 생성자에 접근하려 해도, 디폴트 생성자 밖에

 나오지 않게 됩니다.

 

 이것은 c++ 의 개념과 똑 같습니다. 하지만 자바는 이 문제를 super를 통해 해결합니다.

 인자가 붙은 부모 클래스의 생성자에 나는 접근하고 싶다.라고 할때

 super();를 사용하는겁니다.

 

 즉 인자가 없는 부모 클래스의 생성자에 접근할 때는 super를 사용할 필요가 없습니다

 오직 인자(매개변수)가 있는 부모 클래스에 접근할 때 super를 사용하는 것이죠

 

 이것이 주는 것은 상상합니다.

 방금 생성자 생성 순서를 알아봤죠?

 생성자는 초기화의 기능을 합니다.

 그리고 무조건 적으로 아버지 클래스 부터 생성하고 그다음 아들 클래스를 생성합니다.

 

 

 왜 super()를 가장 처음에 쓰는지 이것으로 이해됐겠죠?

 일단 아버지 클래스가 초기화 되야, 상속받을 수 있고, 그래야 아들 클래스도 구현할 수 있죠.

 

 

그렇다면 우리는 상속 받은 상태에서 아버지 클래스의 초기값을 마음대로 바꾸어 프로그램

 전반을 제어하는 기능을 가질 수 있는 겁니다.

 이 의미까지 이해했다면 상속에 대해 거의 다 이해한 겁니다.

 

 초기값을 바꾼다는 것은 프로그램 자체를 변화시키를 수 있는 거죠.

 그것이 바로 자바가 가지는 가장 큰 장점입니다 상속을 통해 아버지 클래스의 내용을

 변화 시킬 수 있다는 거죠.

 

 사실 이것 외에도 여러가지가 더 있습니다. 하지만 이정도만 알아도 어지간한 개념은 다 이해되어

 넘어갈 것같습니다. 기회가 되면 다음에는 자료구조와 디자인 패턴에 대한 이야기를 더 해보죠.

 그럼 공부 열심히 하세요.

+ Recent posts