안드로이드의 ViewPager 및 BitmapFactory를 이용해서 몇 종류의 이미지들을 모아 보여주는 기능을 구현하였는데

다음과 같이 OOM(Out Of Memory Error)를 맞닥뜨렸다.

 

07-08 00:21:48.309: E/dalvikvm-heap(28645): Out of memory on a 13498800-byte allocation.
07-08 00:21:48.309: I/dalvikvm(28645): "main" prio=5 tid=1 RUNNABLE
07-08 00:21:48.309: I/dalvikvm(28645):   | group="main" sCount=0 dsCount=0 obj=0x40cfc460 self=0x1969d88
07-08 00:21:48.309: I/dalvikvm(28645):   | sysTid=28645 nice=0 sched=0/0 cgrp=default handle=1075004776
07-08 00:21:48.309: I/dalvikvm(28645):   | schedstat=( 0 0 0 ) utm=789 stm=548 core=1
07-08 00:21:48.309: I/dalvikvm(28645):   at android.graphics.Bitmap.nativeCreate(Native Method)
07-08 00:21:48.319: I/dalvikvm(28645):   at android.graphics.Bitmap.createBitmap(Bitmap.java:605)
07-08 00:21:48.319: I/dalvikvm(28645):   at android.graphics.Bitmap.createBitmap(Bitmap.java:551)
07-08 00:21:48.319: I/dalvikvm(28645):   at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:437)
07-08 00:21:48.319: I/dalvikvm(28645):   at android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:524)
07-08 00:21:48.319: I/dalvikvm(28645):   at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:499)
07-08 00:21:48.319: I/dalvikvm(28645):   at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:351)
07-08 00:21:48.319: I/dalvikvm(28645):   at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:374)
07-08 00:21:48.319: I/dalvikvm(28645):   at

...

 

 

07-08 00:21:48.749: E/AndroidRuntime(28645): FATAL EXCEPTION: main
07-08 00:21:48.749: E/AndroidRuntime(28645): java.lang.OutOfMemoryError
07-08 00:21:48.749: E/AndroidRuntime(28645):  at android.graphics.Bitmap.nativeCreate(Native Method)
07-08 00:21:48.749: E/AndroidRuntime(28645):  at android.graphics.Bitmap.createBitmap(Bitmap.java:605)
07-08 00:21:48.749: E/AndroidRuntime(28645):  at android.graphics.Bitmap.createBitmap(Bitmap.java:551)
07-08 00:21:48.749: E/AndroidRuntime(28645):  at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:437)
07-08 00:21:48.749: E/AndroidRuntime(28645):  at android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:524)
07-08 00:21:48.749: E/AndroidRuntime(28645):  at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:499)
07-08 00:21:48.749: E/AndroidRuntime(28645):  at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:351)
07-08 00:21:48.749: E/AndroidRuntime(28645):  at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:374)
07-08 00:21:48.749: E/AndroidRuntime(28645):  at

...

 

이 문제를 해결하기 위해서 이틀 정도를 고생했다.

 

원인은 예상치 못했던 곳에 있었다.

 

어느 정도 감이 있는 사람이라면 다음의 로그를 보고 알아차릴 수 있을 것이다.

 

이 로그는 어플리케이션을 실행해서 이미지들을 보여주는 기능을 수 차례 반복 수행 하면서 기록된 것이다.

 

 

07-08 00:18:30.756: I/_PracticePagerAdapterClass(28645): parent: onPageSelected()=4
07-08 00:18:31.357: I/_PracticePagerAdapterClass(28645): destroyItem: position=1/13, bitmapSize=8191152
07-08 00:18:31.527: I/_PracticePagerAdapterClass(28645): instantiateItem: position=6/13, bitmapSize=8191152
07-08 00:18:43.208: I/_PracticePagerAdapterClass(28645): parent: onPageSelected()=5
07-08 00:18:43.839: I/_PracticePagerAdapterClass(28645): destroyItem: position=2/13, bitmapSize=8992608
07-08 00:18:44.109: I/_PracticePagerAdapterClass(28645): instantiateItem: position=7/13, bitmapSize=18348016
07-08 00:19:15.260: I/_PracticePagerAdapterClass(28645): parent: onPageSelected()=4
07-08 00:19:16.191: I/_PracticePagerAdapterClass(28645): instantiateItem: position=2/13, bitmapSize=8992608
07-08 00:19:16.191: I/_PracticePagerAdapterClass(28645): destroyItem: position=7/13, bitmapSize=18348016
07-08 00:19:32.987: I/_PracticePagerAdapterClass(28645): parent: onDestroy()
07-08 00:19:40.224: I/_PracticePagerAdapterClass(28645): parent: onCreate()
07-08 00:19:41.075: I/_PracticePagerAdapterClass(28645): instantiateItem: position=0/13, bitmapSize=13498784
07-08 00:19:42.716: I/_PracticePagerAdapterClass(28645): instantiateItem: position=1/13, bitmapSize=8191152
07-08 00:19:42.897: I/_PracticePagerAdapterClass(28645): instantiateItem: position=2/13, bitmapSize=8992608
07-08 00:19:54.408: I/_PracticePagerAdapterClass(28645): parent: onDestroy()
07-08 00:20:00.544: I/_PracticePagerAdapterClass(28645): parent: onCreate()
07-08 00:20:01.435: I/_PracticePagerAdapterClass(28645): instantiateItem: position=0/13, bitmapSize=13498784
07-08 00:20:01.585: I/_PracticePagerAdapterClass(28645): instantiateItem: position=1/13, bitmapSize=8191152
07-08 00:20:01.825: I/_PracticePagerAdapterClass(28645): instantiateItem: position=2/13, bitmapSize=8992608
07-08 00:20:06.319: I/_PracticePagerAdapterClass(28645): parent: onDestroy()
07-08 00:21:47.618: I/_PracticePagerAdapterClass(28645): parent: onCreate()

...

 

다음의 이미지에서 확인할 수 있듯이, 페이지는 항상 Instantiate 되었지만 정작 Destroy 되는 순간은

 

현재 보고 있는 페이지가 이동하면서 준비된 페이지의 수가 설정된 최대 준비페이지 수(여기서는 5개)보다 많아졌을 때 뿐이었다.

 

따라서 각각의 Page에 연결되었던 Bitmap Drawable들은 해제되지 못한 채 가비지 컬렉터(GC)에서 처리되지 못 하고 마는 것이다.

 

 

 

 

 

그래서 나는 ViewPager를 포함한 parent에서 onDestory()가 호출될 때 pagerAdapter에 따로 만들어 둔 비트맵 리사이클 메서드를 호출하도록 하였다.

 

결과는 만족스러웠다. 몇 번 액티비티를 들락날락거리면 죽어버리던 앱이 몇 십번을 반복해도 이상이 없었다.

 

참고로, 어떤 식으로 해결했는지에 대해 말하자면

 

adapter 클래스에 전체 페이지 갯수만큼의 크기의 boolean 배열과 ImageView 배열을 만들어서

 

각각의 위치의 페이지들이 instantiate 되었는지 아니면 destory 되었는지 체크해 두었다가

 

parent Activity의 onDestroy()에서 해당 배열을 처리하는 메서드를 호출하는 방법을 사용하였다.

 

예를 들면 다음과 같은 소스코드이다.

 

 

 

 

 

덧붙여 말하자면, ViewPager를 좌, 우로 넘기기만 헀을 뿐인데도 메모리 에러가 생기는 경우는

 

destroyItem()에서 리소스의 뒷처리를 하지 않았기 때문일 가능성이 크다.

 

Posted by Kugi
,



인터넷에서 구한 안드로이드 프로젝트를 이클립스에서 실행시켜보려고 했는데 에러 표시가 생겼다.

 

가장 먼저 눈에 들어오는 에러 원인은 안드로이드 라이브러리 설정이 제대로 되어있지 않을 경우 발생하는 다음의 에러이다.

The import android cannot be resolved

 

Android SDK 및 Build Path 등의 설정이 잘못된 것이라고 생각하여 프로젝트 설정을 변경하려고 했는데 다음과 같은 에러 메시지 팝업이 떴다.

The currently displayed page contains invalid values.

 

 

여러 번 테스트 해 본 결과

 

프로젝트를 선택하여 마우스 오른쪽클릭 후 나오는 팝업메뉴에서 [Properties] 창을 열었을 경우,

  OR

프로젝트를 선택하여 마우스 오른쪽클릭 후 나오는 팝업메뉴에서 [Properties] -> [Android] 항목을 선택했을 경우에

 

창이 제대로 열리지 않고 위와 같은 메시지가 떴다.

 

 

문제를 해결하기 위해 다양한 시도를 하던 중,  이클립스를 다시 실행시켰더니 콘솔에 아래와 같은 메시지가 뜨는 것을 발견하였다.

Project has no project.properties file! Edit the project properties to set one.

 

[project.properties] 파일이 누락되어 있었기 때문에 발생하는 문제였다.

 

이 경우, 간단하게 프로젝트에 [project.properties] 파일을 추가해주면 된다.

 

파일에 아무런 내용이 없어도 문제되지 않는다. 찝찝하다면 다음의 예시를 참고하면 된다.

 

 

 

 

프로젝트에 [project.properties] 파일을 추가하고 나면, 이제 정상적으로 [Properties] 창이 열린다.

 

 

필자의 경우, 라이브러리를 정상적으로 불러오지 못하고 있기 때문에 [Properties] 창의 [Java Build Path]에서 [Libraries] 탭에

 

[Unable to get system library for the project] 라는 항목이 있었는데 이것을 제거한 후에

 

[Properties] -> [Android] 항목을 열어서 다음 이미지와 같이 적당한 버전의 Build Target을 선택해 주었다.

 

* 참고로, 이 항목은 Android SDK 및 플러그인이 설치되어 있어야만 나타난다.

 

 

[Apply] 버튼을 눌러서 적용시키고 나면 [Package Explorer] 및 속성 창에서 프로젝트에 정상적으로 라이브러리가 추가된 것을 볼 수 있다.

 

 

Posted by Kugi
,



소켓을 통해 서버에서 전송받은 .mp4 녹음 파일을 안드로이드 어플리케이션에서 재생하려고 했는데

 

다음과 같은 예외를 만났다.

 

error (1, -2147483648)
java.io.IOException: Prepare failed.: status=0x1
at android.media.MediaPlayer.prepare(Native Method)

 

	if ( player == null )
	{
		player = new MediaPlayer();
		player.setOnCompletionListener(comlistener);
	}
	player.setDataSource(filePath);
	player.prepare();
	player.start();

 

그래서 위와 같이 되어 있던 소스를 다음과 같이 FileDescriptor를 이용하도록 바꾸어보았다.

(참고로, 'player' 변수의 null 검사 부분의 조건문은 이 글에서 별로 중요하지 않다. 단지 변수의 객체 타입을 파악하기 쉽도록 하기 위해 제외하지 않았다.)

 

	if ( player == null )
	{
		player = new MediaPlayer();
		player.setOnCompletionListener(comlistener);
	}
				
	File sourceFile = new File(filePath);
	if ( sourceFile.exists() )
	{
		FileInputStream fs = new FileInputStream(sourceFile);
		FileDescriptor fd = fs.getFD();
		fs.close();
		player.setDataSource(fd);
		player.prepare();
		player.start();
	}

 

그러자 이번엔 다음과 같은 예외가 발생했다.

 

Unable to to create media player
java.io.IOException: setDataSourceFD failed.: status=0x80000000
at android.media.MediaPlayer.setDataSource(Native Method)

 

스트림이 닫히는 순서가 문제일까 싶어서 다음과 같이 FileInputStream의 닫는 순서를 약간 변경해보았다.

 

	if ( player == null )
	{
		player = new MediaPlayer();
		player.setOnCompletionListener(comlistener);
	}
				
	File sourceFile = new File(filePath);
	if ( sourceFile.exists() )
	{
		FileInputStream fs = new FileInputStream(sourceFile);
		FileDescriptor fd = fs.getFD();
		player.setDataSource(fd);
		fs.close();	// 데이터 소스를 설정한 후 스트림을 닫았다.
		player.prepare();
		player.start();
	}

 

이번엔 파일이 정상적으로 다운로드 되어있는 경우, 문제 없이 재생이 되었다.

 

0x80000000 예외일 경우 대부분 파일이 잘못된 경우가 많으니 파일 크기를 확인하거나, 먼저 다른 플레이어로 재생해보는 편이 좋다.

Posted by Kugi
,



자바로 C언어로 구현된 서버와 소켓 통신을 하려던 중 다음과 같은 예외가 발생하였다.

 

java.io.StreamCorruptedException: invalid stream header: 6572726F
   at java.io.ObjectInputStream.readStreamHeader(Unknown Source)
   at java.io.ObjectInputStream.<init>(Unknown Source)

 

 

원인은 예외메시지가 보여주는 것처럼 스트림헤더가 유효하지 않기 때문이었다.

 

C언어 기반의 서버와 통신하기 위해서는 ObjectStream이 아닌 DataStream을 사용해야 하는데

 

습관적으로 ObjectOutputStream과 ObjectInputStream를 사용해서 발생한 문제였다.

 

이것은 왜냐하면 JAVA 기반과 C언어 기반이 소켓통신을 하기 위해서는 read와 write를 할 때 byte 타입으로 해 주어야 하기 때문이다.

 

다음은 C언어 기반의 서버에 소켓을 연결해서 한 번 메시지를 주고받는 JAVA 클라이언트의 예제 소스이다. 서버의 소스는 포함하지 않는다.

 

Example.java

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;

public class Example {

	private static final String serverIP = "1.234.56.78";	// your serverIP
	private static final int portNum = 1234;		// your port number
	private static final int buffersize = 100;

	private Socket mSocket = null;
	private DataOutputStream mObjOStream = null;
	private DataInputStream mObjIStream = null;

	public Example()
	{	
	}
	
	public static void main(String args[])
	{
		Example example = new Example();
		example.connect();
	}

	public void connect()
	{
		try
		{
			mSocket = new Socket();
			mSocket.connect(new InetSocketAddress(serverIP, portNum));
			mObjOStream = new DataOutputStream(mSocket.getOutputStream());
			mObjIStream = new DataInputStream(mSocket.getInputStream());
			System.out.println("connection successed");
			
			byte[] out = new byte[buffersize];
			String sendmgs = "kugistory.net";	// your sending message
			out = sendmgs.getBytes();
			mObjOStream.write(out);
			mObjOStream.flush();
			System.out.println("message sended: " + sendmgs);
			
			byte[] in = new byte[buffersize];
			mObjIStream.read(in,0,in.length);
			String response = new String(in,0,in.length);	// your receiving message
			response.trim();
			
			System.out.println("response=" + response + "@end");
			mSocket.close();
		}
		catch ( Exception e )
		{
			e.printStackTrace();
		}
	}
}

Posted by Kugi
,



SharedPreferences에 값이 제대로 들어가는 지 테스트했었는데

 

테스트 할 당시에는 제대로 값이 남아있는 듯 보였으나 나중에 스마트폰을 재부팅한 뒤 다시보니

 

아무런 데이터도 저장되어있지 않은 것을 발견하였다.

 

확인해본 결과 환경설정->어플리케이션 메뉴에서 어플을 찾아 강제종료버튼(작업관리자에 표시되지 않더라도 활성화되어있다)을 누르고 나면

 

다시 실행했을 때 SharedPreferences 값이 하나도 남아있지 않음을 알 수 있었다.

 

메모리 혹은 임시파일 형태로 저장되는 것이라고 추측된다.

 

해결 방법은 다음과 같다.

 

 

SharedPreferences의 Editor를 사용하기 전에 Editor 객체의 clear() 메소드를 호출했더니 문제가 해결되었다.

 

SharedPreferences.Editor ePref = pref.edit();
ePref.clear();
ePref.putStringSet(XXX , XXX);
ePref.apply();

 

 

Editor에 값을 갱신한 후 commit() 또는 apply()를 빠뜨리면 아예 저장이 되지 않으므로 주의하자.

 

또한 clear()를 호출한 후에는 SharedPreferences 값이 지워지므로 보존해야 할 데이터는 따로 마련한 변수 등에 저장해두어야 한다.

Posted by Kugi
,