Thread(스레드)와 Handler(핸들러) 실험

Posted by ITPangPang
2016. 10. 23. 18:15 안드로이드(android)/Background(백그라운드)



Thread(스레드)와 Handler(핸들러)

실험


ㆍ 이 카테고리에 글을 쓰는게 5달만인것 같네요..

    5월 22일날 Thread handler Looper란 제목으로

    글을 하나 써놓고.. 아에 까먹고 있었습니다..

    

    최근에 한분이 댓글을 달아주셔서 그때서야 생각난..

    

    그래서 오늘에서야 두번째 글을 쓰게 되었습니다.

 

    이 부분은 사실 바로 개발에 도움되기 보다는 개념적인

    부분이라 지금 당장 도움이 되지는 않을 수 있습니다.. 





어쨋든 오늘은

첫번째 글에서 개념적인

부분에 대해서 설명했었는데


그때 글로만 설명드렸던

부분을 코드로 한번 짜볼까 합니다


이번글도 역시.. 

저도 공부하면서 쓰는글이라

틀리는 부분이 있을 수 있으니

발견하는 즉시 지적해주시면

감사하겠습니다


작업스레드는 왜 만들어야 하나요?



개념설명할때 이 부분이 있었죠

작업스레드를 만들어야 하는 이유!


그리고 아래와 같은 코드로 예를

들었습니다

btn.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
for(int i = 0; i<10000000;i++)
{
for(int j = 0; j<1000;j++)
{
tv1.setText("" + i);
}
}
}
});


메인 스레드에서

위와 같은 작업을

하게 될 시에


앱이 멈추고

조금 지나면

경고창이 뜬다


여기서부터 시작을 해보겠습니다


메인스레드는

한가닥 실이라고 했습니다


그래서 위와 같이

시간이 오래걸리는

작업을 지시했을때


실이 하나이기 때문에

그 작업을 완료할때까지는

사용자가 다른 지시를 내려도

(터치를 하거나 뭐 스크롤을 하거나

다른 버튼을 누르거나)


그 지시를 따를 수가 없습니다


그래서

실에 매듭을 묶어서

두 갈래 길로 만들어야 하죠


여기서는 저 생성시점이

버튼을 눌렀을때가 됩니다

public class MainActivity extends AppCompatActivity
{
TextView tv;
Button btn;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

tv = (TextView)findViewById(R.id.tv);
btn = (Button)findViewById(R.id.btn);

btn.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
//작업스레드 생성해줘
startSubThread();
}
});
}

public void startSubThread()
{
//작업스레드 생성(매듭 묶는과정)
MyThread myThread = new MyThread();
myThread.setDaemon(true);
myThread.start();
}

public class MyThread extends Thread
{
@Override
public void run()
{

}
}
}


이제 실이 하나더

생겼으니 이중포문

부분을 작업스레드에

떠넘기면 되겠죠?


public void startSubThread()
{
//작업스레드 생성(매듭 묶는과정)
MyThread myThread = new MyThread();
myThread.setDaemon(true);
myThread.start();
}

public class MyThread extends Thread
{
@Override
public void run()
{
for(int i = 0; i<10000000;i++)
{
for(int j = 0; j<1000;j++)
{
tv.setText("" + i);
}
}

}
}


이렇게 시키면 될까요?




어디서 본 에러메시지 입니다.

첫 글에서 언급된던 내용입니다.

tv.setText("" + i);


이 놈이 문제입니다

작업스레드에서 UI에 접근했을때

위와 같은 에러가 뜹니다.

(왜 그런지는 이전글에서..)


그래서 한번 setText만

주석처리 해보면 에러메시지가

사라집니다


public class MyThread extends Thread
{
@Override
public void run()
{
for(i = 0; i<10000000;i++)
{
for(int j = 0; j<1000;j++)
{
//tv.setText("" + i);
}
}
Log.d("ITPANGPANG","i="+i);
}
}


작업스레드에서

UI변경을 하지 않도록

setText를 주석처리하고


이게 뭐 잘 돌아가나

어쩌나 눈으로 확인해보기

위해 로그로 i값을 한번

찍어보았습니다.



버튼을 누르고

몇 초 뒤에 로그값에


i값으로

10,000,000(천만)이

찍힌 것을 확인 할 수 있었습니다.




위에까지의

작업이

여기까지입니다.


작업스레드 매듭을

하나 묶고

숫자를 세줘!


그 다음 해야할

일은


일을 시켰으니

작업결과를

메인 스레드에

넘겨야 하겠죠?


그래서

메인스레드와 작업스레드가

통신할 수 있도록

핸들러(Handler)를 하나

만들어서 통신해보도록 하겠습니다


public class MainActivity extends AppCompatActivity
{

Button btn;
TextView tv;
int i;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

tv = (TextView)findViewById(R.id.tv);
btn = (Button)findViewById(R.id.btn);

btn.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
//작업스레드 생성해줘
startSubThread();
}
});
}


public void startSubThread()
{
//작업스레드 생성(매듭 묶는과정)
MyRunnable myRunnable = new MyRunnable();
Thread myThread = new Thread(myRunnable);
myThread.setDaemon(true);
myThread.start();
}

android.os.Handler mainHandler = new android.os.Handler()
{
public void handleMessage(Message msg)
{
if (msg.what == 0)
{
tv.setText("" + i);
}
};
};


public class MyRunnable implements Runnable
{
@Override
public void run()
{
while(i<10000000)
{
i++;
try
{
if(i==10000000)
{
Message msg = Message.obtain();
msg.what = 0;
mainHandler.sendMessage(msg);
}
}
catch (Exception e)
{
}
}
}
}
}


코드가 살짝 길어졌는데

Handelr와 Runnable 부분만 보면

    android.os.Handler mainHandler = new android.os.Handler()
{
public void handleMessage(Message msg)
{
if (msg.what == 0)
{
tv.setText("" + i);
}
};
};


이 부분이 중요하겠죠

핸들러는 메인스레드와

통신할 수 있으므로 핸들러에서

TextView에 접근해서 i값을 찍어줍니다


조건이 대신

if(msg.what ==0)

일때 입니다 

public class MyRunnable implements Runnable
{
@Override
public void run()
{
while(i<10000000)
{
i++;
try
{
if(i==10000000)
{
Message msg = Message.obtain();
msg.what = 0;
mainHandler.sendMessage(msg);

}
}
catch (Exception e)
{
}
}
}
}


Runnable쪽을 보면

while문을 통해서


i값을 천만까지

올려주는 작업을 합니다


그리고 i값이 천만이 되는순간

Message msg 객체를 생성해서

핸들러에 그 값을 전달합니다

mainHandler.sendMessage(msg);


msg 객체를 보내주면

Handler에서 객체를 받아서

객체의 값이 0인지 확인하고

true면 UI를 변경시켜 줍니다.



1초마다 메인스레드와 작업스레드 통신


이번에는 거의

같은코드이지만

몇줄만 수정해서


1초마다 화면에

숫자를 카운트 하는

코드를 만들어보겠습니다.


public class MainActivity extends AppCompatActivity
{

Button btn;
TextView tv;
int i = 0;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

tv = (TextView)findViewById(R.id.tv);
btn = (Button)findViewById(R.id.btn);

btn.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
//작업스레드 생성해줘
startSubThread();
}
});
}


public void startSubThread()
{
//작업스레드 생성(매듭 묶는과정)
MyRunnable myRunnable = new MyRunnable();
Thread myThread = new Thread(myRunnable);
myThread.setDaemon(true);
myThread.start();
}

android.os.Handler mainHandler = new android.os.Handler()
{
public void handleMessage(Message msg)
{
if (msg.what == 0)
{
tv.setText("" + i);
}
};
};


public class MyRunnable implements Runnable
{
@Override
public void run()
{
while(true)
{
i++;
Message msg = Message.obtain();
msg.what = 0;
mainHandler.sendMessage(msg);
try
{
Thread.sleep(1000);
}

catch (Exception e)
{
}
}
}
}
}


거의 달라진 것은

없습니다


Thread.sleep(1000);

가 새로 생겼다는 점 정도?


Thread.sleep은

느낌 그대로 1초동안

잠시 재우는 역할을 합니다.


하지만 sleep은 좀 알아둬야

하는 부분이 있습니다.


일단 위에 코드 결과를 먼저 보면


작업스레드가 생성되고

1초마다 핸들러를 통해서

UI를 업데이트 하는 화면입니다.



위에서 sleep에 대해 알아둬야할점

이라고 적었는데.


사실 sleep으로 일정시간을

제어하는것은 정확도가 떨어지는

부분이 있습니다.


디바이스 마다 다를수 있지만

전원버튼을 눌러서 Screen이 off

되었을때  thread.sleep이 정확도 100%

보장을 못합니다.


위에 코드대로라면

10이 찍힌상황에서 화면을 잠시 off하고

60초뒤에 화면을 켰을때 70이란 숫자가

찍혀야 하지만 예상과 다르게

60이라던가 65라던가 68등 다른숫자가

찍힐 가능성이 있습니다.


간단한 코드에서는 sleep을 써도

되지만 정확성을 요구하는 코드에서는

sleep 보다는 System쪽 시간을 건드려야

합니다

(휴대폰이 켜진 시간, 누적시간 등)


예를 들어

SystemClock.elapsedRealtime()

이것을 이용하면 정확한 1초를

계산할 수 있습니다.

(이 부분은 안드로이드-System

카테고리에 예~~전에 한번 썼던

기억이 있네요)


아니면

CountDownTimer를

잘 사용하면

위와 똑같은 결과를

만들 수 있습니다.



이번글은 이정도로

마무리 짓도록 하겠습니다.


깔끔하게 쓰고 싶었는데

뭔가 정리가 안 된 느낌이지만..



다음글에서는 좀 더 다듬어서

오도록 하겠습니다.