반응형

 

이번에는 국가 코드를 가져오는 법을 알아볼 것이다.

다국어를 지원하는 앱을 개발하는 분들 중 단순히 텍스트 번역만이 아닌,

각 나라별로 프로그램의 동작이 바뀌어야 하는 등의 경우도 존재할 것이다.

그런 경우 어느 국가에서 실행했는지를 알아야 하는데,

이것을 국가 코드를 얻어와서 해결할 수 있다.

 

        Locale locale;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            locale = getApplicationContext().getResources().getConfiguration().getLocales().get(0);
        }else{
            locale = getApplicationContext().getResources().getConfiguration().locale;
        }
        String displayCountry = systemLocale.getDisplayCountry();
        String country = systemLocale.getCountry();
        String language = systemLocale.getLanguage();
 

코드는 위와 같다.

API Level 24 안드로이드 7.0(Nougat)부터는 getConfiguration()의 locale이 Deprecated되었기 때문에,

위처럼 조건으로 getLocales().get(0)을 사용해주어야 한다.

 

그 다음 코드를 보면 국가 코드를 가져오는 방법이 세가지가 있다.

 

첫번째 getDisplayCountry()는 나라 이름을 반환해준다.

한국이라면 "대한민국"

미국이라면 "United States"

이런 형식이다. 각 나라별 언어 문자로 반환을 하기 때문에 조금 불편할 수 있다.

 

두번째 getCountry()는 국가 코드를 반환해준다.

나의 경우는 이 국가 코드를 이용해서 국가를 구분한다.

한국이라면 "KR"

미국이라면 "US"

일본이라면 "JP"

중국이라면 "CH"

대만이라면 "TW"

이렇게 각 나라별로 고유의 국가 코드를 반환해준다.

 

세번째 getLanguage()는 국가 언어 코드를 반환해준다.

한국은 "ko"

미국은 "en"

일본은 "ja"

중국은 "zh"

대만도 "zh"

나라별로 국가 언어 코드를 반환해주지만,

중국과 대만은 구분되지 않는 것을 알 수 있다.

 

결론적으로 국가별 코드를 확실히 구분하려면 getCountry()를 사용하는 것을 추천한다.

 

 

반응형
반응형

 

안드로이드 핸들러를 잘 모르고 사용하시는 분들 중 제목과 같은 현상을 겪은 사람이 많을 것이다.

핸들러의 주 목적은 서브 쓰레드에서 주 쓰레드로 데이타를 전달해주는 것이다.

 

 예를 들면 아래와 같다.

토스트 같은 경우는 안드로이드에서 메세지 띄워주는 용도로 매우 유용한데,

메인 쓰레드에만 사용할 수 있으며 그 외의 쓰레드에서 사용하게 되면 오류를 내며 튕겨버린다.

서브 쓰레드에서 쓰기 위해 보통 아래와 같이 핸들러를 구성하고

서브 쓰레드에서 ProcessMessage() 함수를 호출하면 정상적으로 토스트가 동작한다.

 

하지만 코드에서 핸들러 부분이 블럭 처리되어있어서 무언가 찝찝하다.

마우스를 한번 갖다대보자.

 

핸들러를 정적으로 사용하지 않으면 메모리 릭이 발생할 수 있다는 의미이다.

'More'를 눌러서 더 자세한 내용을 보면 아래와 같다.

 

This Handler class should be static or leaks might occur (anonymous android.os.Handler) less... (Ctrl+F1)
Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.

 

정리하면 핸들러는 내부 클래스로 선언되어 가비지 컬렉터에 의해 소멸되지 않을 수 있다는 것이다.

그래서 핸들러를 정적 내부 클래스로 선언하면 해당 이슈를 해결할 수 있다.

추가로 정적 내부 클래스로 선언하게 되면, 외부 클래스의 정적멤버 외에는 접근할 수 없다.

그래서 혹시나 외부 클래스의 멤버에 접근하고 싶다면 WeakReference 객체를 이용하면 된다.

여기서는 단순히 토스트를 띄우는 것이 목적이므로 해당 객체를 사용하지는 않을 것이다.

 

서론이 길었는데 어쨌든 해결 방법은 간단하다.

위의 핸들러 관련 코드를 아래와 같이 수정하면 된다.

 

    private final MyHandler handler = new MyHandler();

    private static class MyHandler extends Handler {
        public MyHandler() {
        }

        @Override
        public void handleMessage(Message msg) {
            if(msg.arg1 == 1)
                Toast.makeText(Global.getContext(), "오류가 발생하였습니다.", Toast.LENGTH_SHORT).show();
            else if(msg.arg1 == 2)
                Toast.makeText(Global.getContext(), "정상적으로 처리되었습니다.", Toast.LENGTH_SHORT).show();
        }
    }

 

이처럼 단순히 정적 내부 클래스로 선언하면 된다.

그럼 메모리 릭의 걱정도 없고, 문제 없이 핸들러를 사용할 수 있다.

반응형
반응형

 

개발자들이 시간 측정의 용도로 GetTickCount()를 애용하고 있다.

 

정밀도 높은 시간 측정은 아니지만 편리한 맛에 많이 사용되고 있다.

 

GetTickCount() 함수를 간단하게 설명하자면, 이 함수는 윈도우즈가 부팅된 이후부터 시간이 카운팅되는 함수이며

 

수치는 1ms 단위로 카운팅된다. 즉, 윈도우즈 부팅 후 1초가 지났다면 반환 값은 1000이 된다.

 

하지만 이 함수는 한계가 있는데 바로 반환 시간이 최대 DWORD(32비트)라는 것이다.

 

날짜로 따지면 49.7일인데, 49.7일 동안 윈도우즈를 종료하지 않을 경우 0으로 초기화되는 것이다.

 

unsigned long Timer = GetTickCount();

while(true)
{
	if(GetTickCount() >= Timer + 1000) //1초가 지났는가?
	{
	    printf("1초 지남");
	    Timer = GetTickCount();
	}
}

위와 같은 코드가 있다고 하자.

 

1초에 한번씩 1초가 지났다는 메세지를 띄워주게 된다.

 

그런데 49.7일이 거의 다 된 상태에서 만약 "1초 지남"을 프린트하고 Timer 변수를 갱신한 뒤에,

 

49.7일이 지나 GetTickCount()가 0으로 초기화된다면?

 

즉, 예로 Timer = 10000이 들어간 상태에서 GetTickCount()가 0으로 초기화가 된다면

 

1초가 지날 때마다 조건을 만족해야함에도 불구하고 초기화로 인해서 10초를 더 기다려야 하는 상황이 발생한다.

 

49.7일 이전에 윈도우즈만 재부팅한다면 이런 것을 걱정할 필요는 없으나, 서버 등 계속 켜놓아야 하는 상황에서는

 

이런 상황이 발생할 수 있다.

 

그래서, 그 대안으로 GetTickCount64() 함수를 쓸 수 있다. 기존 GetTickCount()에서 범위만 커진 것이다.

 

64비트로 확장되었기 때문에, 5억년이 넘는다. 즉, 아무리 PC를 켜놓아아도 위와 같은 문제를 일으킬 일이 없다고 봐도 된다.

 

unsigned __int64 Timer = GetTickCount64();

while(true)
{
	if(GetTickCount64() >= Timer + 1000) //1초가 지났는가?
	{
	    printf("1초 지남");
	    Timer = GetTickCount64();
	}
}

이와 같이 변수 타입과 함수명만 바꾸어주면 되니 매우 간단하다.

 

결론적으로 GetTickCount()를 사용할 경우 상황에 따라, GetTickCount64() 사용하는 것도 고려해야할 것이다.

 

반응형
반응형

안드로이드에서 빌드할 때 INSTALL_FAILED_OLDER_SDK 오류가 발생하는 경우가 있다.

 

보통 이 경우는 안드로이드 스튜디오에서 설정한 안드로이드 SDK 버전과 타겟의 버전 안맞는 경우인데,

 

일반적으로 build.gradle의 minSdkVersion을 낮게 설정하여 해결이 되지만,

 

필자의 경우는 다른 경우였다.

 

build.gradle 파일 내 최하단에 보면 아래와 같은 코드가 있다.

 

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.2.1'
}

 

필자의 경우는 여기서 appcompat에 대한 버전이 옳지 않아서 발생하는 오류였다.

 

 compile 'com.android.support:appcompat-v7:22.2.1'

 

 compile 'com.android.support:appcompat-v7:22.1.0'으로 수정한 뒤 해당 문제가 해결되었다.

 

반응형
반응형

 

안드로이드 스튜디오에서 AppCompatActivity 를 상속받을 때 해당 클래스를 찾을 수 없다고 발생할 경우의 해결 방법이다.

 

build.gradle 파일을 열면 하단에,

 

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:21.0.3'
}

이와 같이 입력되어있을 것이다. 아래와 같이 appcompat의 버전을 바꾸어준다.

 

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.1.0'

}

 

이후 해당 build.gradle을 Sync Now 시켜주면 해결된다.

반응형
반응형

TW2836 칩은 4채널 비디오 제어가 가능한 IC이다. 가격도 13불이나 된다.

 

4개 채널을 입력 받아서 한 채널로 출력을 내보내주는데 4CH 블랙박스를 예로 들 수 있다.

 

LCD에 4채널 비디오를 화면에 4분할해서 표시하는 역할을 이 IC가 하게 된다.

 

급히 ABOV MCU로 해당 칩을 제어할 일이 생겼었는데, 해당 칩의 내장된 I2C 기능을 쓰기에는 시간도 부족하였고 스터디가 안된 상태여서 그냥 GPIO I2C로 제어를 해보았다.

 

I2C는 비교적 SPI 통신보다는 훨씬 간단하다고 생각된다.. 그리고 클럭 주파수가 정확히 안맞더라도 알아서 잘 동작하는 경우가 많다.

 

대부분 I2C 통신 규격이 비슷하지만 칩마다 타이밍차트가 조금씩 다르고 프로토콜도 조금씩 다르므로 데이타시트를 유심히 봐야한다.

 

 

 

위는 TW2836 칩의 I2C 프로토콜이다.

 

내가 이 칩을 컨트롤하면서 좀 이상하다고 생각되는 부분이 READ 프로토콜쪽이다.

 

READ 프로토콜을 보면 DATA를 2바이트씩 읽을 수 있게 되어있다.

 

그래서 난 당연히 앞의 DATA를 상위 8비트에 넣고 뒤의 DATA를 하위 8비트에 넣어서 16비트 데이타로 받았는데,

 

값은 이상하게 출력되었다. 그래서 오실로스코프로 확인해본 결과 데이타가 8비트로 들어오는 것이었다...

 

확인안해보았으면 삽질만 하고 있을 뻔 했다. 데이타시트가 잘못된 것인지, 다른 의도가 있는 건지는 모르겠다..

 

결국은 앞의 DATA 후 ACK 체크하는 부분을 빼버리고, DATA 후 NACK만 체크하여 READ를 구현하니 잘 되었다.

 

 

 

위 이미지는 타이밍차트이다. 통신 프로토콜을 맞게 보내더라도 위 타이밍이 안맞으면 칩은 응답하지 않는다.

 

보통은 대충 딜레이 넣어서 통신해도 다 타이밍 내에 들어오기 때문에 문제될 일은 드문데 그래도 마이컴의 동작주파수 등 여러 조건을 잘 따져보아야 삽질을 방지할 수 있다.

 

아래는 GPIO I2C로 제어한 코드이다.

 

void i2c_init(void)
{
	P0IO |= 0xC0;  //Set SCL, SDA to output
	SCL = 1;  // Set SCL, SDA High
	SDA = 1;
}

void i2c_start(void)
{
	//start
	P0IO |= 0xC0;  //Set SCL, SDA to output
	SDA = 1;
	SCL = 1;  // Set SCL, SDA High
	Delay_us(7);

	SDA = 0;	// Clear SDA
	Delay_us(7);
 	SCL = 0;	// Clear SCL
	Delay_us(7);
}

void i2c_stop(void)
{
	P0IO |= 0x80;	// Set SDA to output
	SDA = 0;	// Clear SDA Low
	Delay_us(7);

	SCL = 1;	// Set SCL High
	Delay_us(7); 
	SDA = 1; // Set SDA High

 	P0IO &= (~0xC0);	// Set SDA to Input
}

void write_i2c_byte(unsigned char byte)
{
	unsigned char i = 0;

	P0IO |= 0x80;		// Set SDA to output
	
	for (i = 0; i < 8 ; i++)
	{
		if((byte & 0x80)==0x80)	SDA = 1;	// Set SDA High
		else					SDA = 0;	// Clear SDA Low

		SCL = 1;		// Set SCL High, Clock data
		_nop_();
		byte = byte << 1;	// Shift data in buffer right one
		SCL = 0;		// Clear SCL
		_nop_();
	}
	SDA = 0; //listen for ACK
	P0IO &= (~0x80);

	SCL = 1;		
	_nop_();_nop_();
	SCL = 0; 
	_nop_();_nop_(); //Clear SCL.
	P0IO |= 0x80;		// Set SDA to Output
}

unsigned char read_i2c_byte(unsigned char ch)
{
	unsigned char i, buff=0;

	P0IO &= (~0x80);	// Set SDA to input

	for(i=0; i<8; i++)
	{
		_nop_();_nop_();
		SCL = 1;	
		_nop_();_nop_();// Set SCL High, Clock bit out
		buff <<= 1;

		// Read data on SDA pin
		if ((P0&0x80) == 0x80) {
			buff |= 0x01;
		}
		SCL = 0; // Clear SCL
		_nop_();_nop_();
	}
   	if(ch == 0) //ACK
	{
		SDA = 1; //SDA HIGH.
	}
	else //NACK.
	{
		SDA = 0; //SDA LOW.
	}
	SCL = 1;		
	_nop_();_nop_();
	SCL = 0; //SCL LOW.
	SDA = 1; //SDA HIGH.
	_nop_();_nop_();
	P0IO |= 0x80;	// Set SDA to Output

	return buff;
}

unsigned int read_word(unsigned char slave, unsigned char page_address, unsigned char index_address)
{
	unsigned int temp;
	unsigned char ucHibyte = 0;
	unsigned char ucLobyte = 0;
	unsigned char ucNack = 0;

	ucHibyte=0;
	ucLobyte=0;

	i2c_start();
	write_i2c_byte(slave);
	write_i2c_byte(page_address);
	write_i2c_byte(index_address);

	i2c_start();
	write_i2c_byte(slave+1);

	ucLobyte=read_i2c_byte(1);
	//ucHibyte=read_i2c_byte(1);
	
	i2c_stop();

   	temp = (ucHibyte<<8) + ucLobyte;

	return temp;
}

void write_word(unsigned char slave, unsigned char page_address, unsigned char index_address, unsigned char value)
{
	i2c_start();
	write_i2c_byte(slave);
	write_i2c_byte(page_address);
	write_i2c_byte(index_address);
	write_i2c_byte(value);
	i2c_stop();
}

 

 

 

반응형
반응형

 

이번에는 RECT 충돌 방법에 대해 알아보려고 한다.

 

일반적으로 타일 방식 맵을 사용하는 게임에서 사각형 충돌을 많이 사용한다.

 

가장 기초가 되는 충돌 방식이고 이를 응용하여 다양한 충돌 방식을 생각해볼 수도 있다.

 

 

 

위 이미지는 사각형 충돌이 다양한 형태로 발생할 수 있다는 것을 보여준다.

 

초록색 사각형이 캐릭터이고 빨간색 사각형이 장애물이라 가정해보자.

 

캐릭터는 밑으로 부딪힐 수도 있고 좌우 혹은 위로 부딪힐 수가 있다.

 

이것을 어떻게 처리해야 모든 충돌 조건을 처리할 수 있을까?

 

그전에 RECT의 기본 속성부터 알아보자. RECT 속성은 4개가 존재한다. (LEFT, TOP, RIGHT, BOTTOM)

 

LEFT(X1)은 사각형의 왼쪽 모서리를 의미한다.

TOP(Y1)은 사각형의 위쪽 모서리를 의미한다.

RIGHT(X2)는 사각형의 오른쪽 모서리를 의미한다.

BOTTOM(Y2)는 사각형의 아래쪽 모서리를 의미한다.

 

초록색 사각형의 특정 모서리가 빨간색 사각형의 특정 모서리를 넘어서는지 조건을 비교하여 충돌 처리 함수를 만들 수가 있다.

 

C++에서는 IntersectRect() 함수를 제공해주어서 편리하지만, 자바에서는 아쉽게도 직접 만들어써야한다.

 

아래는 두개의 사각형을 비교하는 충돌 처리 함수이다.

 

public static boolean IsIntersect(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2)
{
        if(x1 + w1 >= x2 && x1 <= x2 + w2 && y1 + h1 >= y2 && y1 <= y2 + h2)
        {
            return true;
        }

        return false;
 }

 

함수 인수를 RECT 속성으로 나열해버려서 그런지 조금 복잡해보이는 감이 있지만, 코드 자체는 무진장 간단하다.

 

조건식을 풀어보면, 아래와 같다.

 

아래 조건 하나하나 위의 그림 충돌 상태에도 적용이 되는지 확인해보자.

 

1. 첫번째 사각형의 오른쪽 모서리가 두번째 사각형의 왼쪽 모서리를 넘어야 한다.

2. 첫번째 사각형의 왼쪽 모서리가 두번째 사각형의 오른쪽 모서리를 넘지 않는다.

3. 첫번째 사각형의 아래쪽 모서리가 두번째 사각형의 위쪽 모서리를 넘어야 한다.

4. 첫번째 사각형의 위쪽 모서리가 두번째 사각형의 아래쪽 모서리를 넘지 않는다.

 

맨 위의 그림 4개를 보면 위 조건 4개가 모두 만족하여 true를 반환하게 된다.

 

그 외의 조건은 당연히 충돌되지 않으므로 false를 반환하게 된다.

 

반응형
반응형

 

안드로이드에서 터치 기능에 대해 구현할 때 onTouchEvent() 함수를 Override하여 사용하게 된다.

 

일반적으로 동시 터치가 아닌 한 포인트의 터치만 구현할 경우 아래와 같이 사용을 한다.

 

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch(event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                    global_x = (int) (event.getX());
                    global_y = (int) (event.getY());
                    break;

            case MotionEvent.ACTION_MOVE:
                    global_x = (int) (event.getX());
                    global_y = (int) (event.getY());
                    break;
        }

        return true;
    }

 

 

간단히 설명을 하면 발생한 이벤트를 마스킹하여 현재 어떤 모션 이벤트(ACTION_DOWN? ACTION_MOVE)가 발생했는지 확인하여 그에 적절한 처리를 하게 된다.

 

위의 코드는 한 포인트에 대해서만 처리될 뿐 두 개 이상의 포인트에 대해서는 고려되있지 않다.

 

아래 코드는 두 개의 포인트에 대해 터치를 구현한 코드이다.

 

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int pointer_count = event.getPointerCount(); //현재 터치 발생한 포인트 수를 얻는다.
        if(pointer_count > 2) pointer_count = 2; //3개 이상의 포인트를 터치했더라도 2개까지만 처리를 한다.

        switch(event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN: //한 개 포인트에 대한 DOWN을 얻을 때.
                    global_num[0] = event.getPointerId(0); //터치한 순간부터 부여되는 포인트 고유번호.
                    global_x[0] = (int) (event.getX());
                    global_y[0] = (int) (event.getY());
                }
                break;

            case MotionEvent.ACTION_POINTER_DOWN: //두 개 이상의 포인트에 대한 DOWN을 얻을 때.
                for(int i = 0; i < pointer_count; i++) {
                    global_num[i] = event.getPointerId(i); //터치한 순간부터 부여되는 포인트 고유번호.
                    global_x[i] = (int) (event.getX(i));
                    global_y[i] = (int) (event.getY(i));
                }
                break;

            case MotionEvent.ACTION_MOVE:
                for(int i = 0; i < pointer_count; i++) {
                    global_num[i] = event.getPointerId(i);
                    global_x[i] = (int) (event.getX(i));
                    global_y[i] = (int) (event.getY(i));
                }
                break;
        }

        return true;
    }

 

코드가 크게 길어지지도 않았고, 그리 어렵지도 않다.

 

3번째 코드에서 event.getPointerCount() 함수로 현재 터치된 포인트 수를 가져온다.

 

두 개의 손가락으로 터치하였다면 2를 반환할 것이며, 그 이상의 터치를 발생시켰을 경우 그에 대한 값을 반환할 것이다.

 

위의 코드에서는 두 개의 포인트에 대해서만 처리할 것이므로 4번째 줄에서 3개 이상의 포인트가 인식되었더라도 강제로 2개의 포인트만 처리하도록 하였다.

 

7번째줄과 15번째 줄을 보면 ACTION_DOWN, ACTION_POINTER_DOWN 이렇게 두개로 나눠져있다.

 

주석에도 설명되어있지만 ACTION_DOWN은 한 개에 대한 포인트에 대해 DOWN 이벤트가 발생했을 경우이고,

 

ACTION_POINTER_DOWN은 두 개 이상에 대한 포인트에 대해 DOWN 이벤트가 발생했을 경우이다.

 

이게 무슨 의미냐면, 두 개 이상의 터치가 발생했을 때는 ACTION_DOWN 이벤트는 발생하지 않는다.

 

반대로 한 개의 터치가 발생했을 때는 ACTION_POINTER_DOWN 이벤트가 발생하지 않는다.

 

즉, 구현하려는 어플이 두 개 이상의 포인트를 동시에 터치할 경우도 있고, 한 개의 포인트만을 터치할 경우도 있다면

 

위와 같이 두 개의 이벤트를 모두 구현해주어야 하는 것이다.

 

왜 이렇게 비효율적으로 구현했는지가 의문이지만 어쨌든 이렇게 구현을 해주어야 한다.

 

다행히도 ACTION_MOVE 이벤트의 경우는 한 개만 터치할 경우나 두 개 이상을 터치할 경우나 모두 같은 이벤트로 들어오게 되므로 별도로 나눠서 구현할 필요가 없다.

 

다음으로 8번째 줄을 보면 ACTION_DOWN의 경우는 당연히 한 포인트를 터치했을 때만 발생하므로 0번 배열에 해당 좌표 데이타를 보관하게 된다.

 

15번째 줄을 보면 발생한 포인터 수만큼 for문을 돌려 배열에 해당 좌표들을 저장하게 된다.

 

여기서 중요한 것이 16번째 줄을 보면 event.getPointerId(i)로 현재 발생한 포인터에 대한 고유 번호를 따로 저장해주고 있다.

 

이것이 왜 필요하냐면, 만약 2개 포인트를 터치하고 있다고 가정해보자.

 

global_num[0] = 0

global_x[0] = 50

global_y[0] = 50

 

global_num[1] = 1

global_x[1] = 180

global_y[1] = 180

 

위 상태에서 처음 터치한 손가락을 떼면 어떻게 될까?

 

global_num[0] = 1

global_x[0] = 180

global_y[0] = 180

 

global_num[1] = 0

global_x[1] = 0

global_y[1] = 0

 

1번 배열에 있던(두번째로 터치한 포인트) 좌표 데이타가 0번 배열로 넘어갔다.

 

당연히 손가락 하나를 뗐으므로 한 포인트만 터치되고 있는 상태로 바뀌었을 것이므로,

 

1번 배열의 데이타는 사라지고 현재 터치되고 있는 좌표 데이타가 0번 배열의 데이타로 넘어가게 된 것이다.

 

하지만 배열의 순서가 바뀌었더라도 터치 상황을 문제없이 인지할 수 있는 방법은 바로 고유 번호의 확인이다.

 

배열의 순서는 바뀌었지만, 고유 번호는 그대로 1을 유지한 채로 넘어가있다.

 

따라서, 다중 터치를 구현할 때 배열의 인덱스를 이용해서 처리하지 않고 고유 포인터 아이디 값을 확인하여 처리해야 문제되지 않는다.

 

위의 내용을 이해한다면 터치 포인트가 3개 이상인 것도 얼마든지 구현이 가능하다.

 

주의할 점을 다시 정리해보면 아래 두가지 정도인 듯 하다.

 

1. ACTION_DOWN은 터치 포인트가 1개일 때만 발생하고 ACTION_POINTER_DOWN은 터치 포인트가 2개 이상일 때만 발생하므로, 필요에 따라 두 가지를 모두 구현해주어야 한다.

 

2. 다중 터치 도중 하나 이상의 터치 포인트가 해제될 경우 좌표 변수 안에 입력되있는 좌표 데이타의 배열 순서가 뒤바뀔 수 있으므로 터치 포인터의 고유 번호를 비교하는 방식으로 구현한다.

 

반응형
반응형

 

SharedPreferences 객체는 제목 그대로 환경 설정 데이타를 간단히 저장할 수 있는 객체이다.

 

물론, 굳이 이것을 사용하지 않더라도 직접 파일 형태로 저장하여 관리할 수도 있다.

 

하지만 객체에서 지원해주는 만큼 간편하게 사용할 수 있기 때문에 간단한 내용을 저장할 때는 자주 사용된다.

 

나의 경우는 유틸 클래스를 별도로 만들어서 이 객체를 통해 환경설정 내용을 불러오거나 저장할 수 있도록 구현해놓고 사용한다.

 

package com.project.core;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;

 

public class Util {
    public static void ConfigSave(Context context, String Name, String Value){
        SharedPreferences pref = context.getSharedPreferences("Conf", 0);
        SharedPreferences.Editor editor = pref.edit();
        editor.putString(Name, Value);
        editor.commit();
     }


     public static String ConfigLoad(Context context, String Name){
         SharedPreferences pref = context.getSharedPreferences("Conf", Activity.MODE_PRIVATE); 
         String data = pref.getString(Name, "");
         return data;
     }
}

나의 경우는 위와 같이 구현해놓고 사용한다.

 

[저장할 때]

ConfigSave(context, "TEST", "1"); //환경 변수 TEST에 1을 저장한다.

 

[불러올 때]

String test = ConfigLoad(context, "TEST"); //환경 변수 TEST의 내용을 불러온다.

 

추가로, SharedPreferences 객체의 내용을 모두 비우거나, 저장한 특정 환경변수의 내용을 제거할 수도 있다.

SharedPreferences의 remove() 함수를 사용하면 특정 환경변수의 내용을 제거할 수 있으며,

clear() 함수를 사용하면 모든 내용이 제거된다.

함수 사용 후에는 반드시 commit() 함수를 호출 시켜주어야 한다.

 

 

반응형
반응형

예를 들어서 안드로이드 클라이언트가 다른 외부 서버랑 연결해서 소켓 연결 후 통신을 주고 받는데,

 

아스키코드를 주고 받을 때는 문제가 없지만 한글을 주고 받을 때는 깨지는 현상이 생깁니다.

 

이것은 인코딩과 관련된 문제로 주고 받을 때 클라이언트쪽에서 송수신할 때에 아래와 같이 처리를 해주면 됩니다.

 

보통 Buffered 객체를 많이 쓰므로 이것으로 예를 들겠습니다.

 

//소켓 연결 처리.

SocketAddress remoteAddr=new InetSocketAddress("127.0.0.1",1234);

Socket socket=new Socket();

socket.connect(remoteAddr); //remoteAddr

 

//Buffered 객체 연동.

BufferedOutputStream out=new BufferedOutputStream(socket.getOutputStream()); //output stream

BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream(),"EUC_KR")); //input stream.

 

위 코드를 보면 out은 서버로 데이타를 내보낼 때 쓰고, in은 서버에서 데이타를 받을 때 씁니다.

Reader 객체 생성시 EUC_KR를 주게 되면 서버에서 받는 데이타는 한글이라도 깨지지 않습니다.

 

전송할 때는

 

String data="한글테스트\n";

out.write(data.getBytes("EUC_KR"));

 

이와 같이 EUC_KR 로 인코딩하여 전송하면 해결됩니다.

 

간단한 내용이지만 이것으로 헤매는 분들이 계시기에 올려둡니다...

 

반응형

+ Recent posts