퉁탕퉁탕 만들어보자

Android Process And Thread 본문

Computer/Android

Android Process And Thread

호숀티 2022. 4. 24. 00:23
반응형

* Process / Thread

Process는 각각의 독립된 메모리 영역(code, data, heap, stack)을 갖고있음

Process하나당 최소 1개의 메인스레드를 갖고있음

Thread는 하나의 프로세스 내에서 실행되는 흐름의 단위로, Process 안에서 코드, 데이터, heap을 공유하고 stack만 별도로 가진다. 따라서 같은 프로세스 안에서 백그라운드 작업을 thread로 수행시키고 대신 동시에 같은 값을 변경하거나 하지 않도록 신경써서 코드를 작성한다.

 

Android 시스템은 일반적으로 단일 thread로 애플리케이션의 Linux 프로세스를 시작하게된다. 기본적으로 같은 애플리케이션의 모든 구성 요소는 같은 프로세스와 같은 스레드에서 실행된다. (main thread)

하지만 애플리케이션 내의 여러 가지 구성 요소가 각자 별도의 프로세스에서 실행되도록 할 수도 있고, 추가 스레드를 만들어서 돌리기도한다.

 

Process

기본적으로 같은 애플리케이션의 모든 구성 요소는 같은 프로세스와 스레드에서 실행된다.

하지만 AndroidManifest에서 각 구성요소들 <activity>, <service>, <receiver>, <provider> 를 선언할 때 android:process 값을 넣어서 실행될 프로세스를 지정할 수 있다. (지정하지 않으면 main process에서 실행됨)

 

이 속성에 할당된 이름이 (":")으로 시작하면 app전용 새 프로세스가 만들어지고 여기서 활동이 실행된다. 

이름이 소문자로 시작하면 그 이름으로 전역 프로세스에서 실행된다. (권한 필요)

 

이렇게 해서 어떤 activity와 어떤 activity 몇개만 다른 프로세스에서 실행하게 한다던지 할 수가 있다.

또한, android:process를 설정하여 다른 app 의 구성 요소를 동일한 프로세스에서 실행할 수도 있다고 한다. (단, 이는 App이 동일한 Linux 사용자 ID를 공유하고 동일한 인증서로 서명되었을 경우에 한함)

 

<application> 요소도 android:process 특성을 지원하여, 모든 구성 요소에 적용되는 기본값을 설정한다.

 

메모리가 부족하거나 하는 경우에 특정 프로세스가 강제로 종료될 수가 있다. 그러면 중단된 프로세스에서 실행되고 있던 App의 구성 요소도 같이 소멸된다. 또한 구성 요소에 수행할 작업이 다시 생기면 그에 대한 프로세스도 다시 시작이된다.

 

어떤 프로세스를 종료할지 결정할 때, Android 시스템은 상대적 중요성을 가늠하여 판단하게 된다. (눈에 보이냐, 안보이냐. 그리고 프로세스에 포함된 구성요소의 상태 - onPause됫냐? 등등)

Thread

App이 시작될 때 시스템이 실행 스레드를 생성하며, 이를 main thread라고 한다.

이 스레드는 UI 관리를 맡기 때문에 일반적으로 UI thread라고 불린다. (그러나 특수한 상황에서 앱의 기본 스레드가 UI 스레드가 아닐 수도 있음.)

 

같은 프로세스에서 실행되는 모든 구성 요소는 UI 스레드에서 인스턴스화되고 각 구성 요소에 대한 system call은 해당 스레드에서 된다. 시스템 콜백에 응답하는 메서드(onKeyDown() 또는 수명 주기 콜백 메서드- onPause, onResume등등)는 항상 프로세스의 UI 스레드에서 실행된다.

 

그런데 App을 단일 스레드로 개발해서 네트워크 액세스나 데이터베이스 쿼리 같은 긴 작업을 ui thread에서 전부 수행한다면 전체 UI thread 가 차단되고, (*스레드가 차단되면 드로잉 이벤트를 포함하여 모든 이벤트가 발송되지 않음) 이러면 사용자에게는 App이 멈춘것처럼 보이게된다. 만약에 UI 스레드가 몇 초 이상 차단되면(현재 약 5초) 사용자에게 "애플리케이션이 응답하지 않습니다"(ANR) 다이얼로그도 뜨게된다. 그러면 보통은 사용자가 영문을 알 수 없기 때문에 앱을 강제종료 하게 된다.

 

UI 스레드를 차단하지 마세요.

 

Android UI 도구 키트는 스레드로부터 안전하지 않기 때문에 UI를 백그라운드 스레드에서 조작해서는 안된다. 사용자 UI 조작 작업은 반드시 !전부! UI 스레드에서 해야만 한다. 

UI 스레드 외부에서 Android UI 컴포넌트에 액세스하지 마세요.

 

Worker Thread

위에서 본것처럼 UI 스레드를 차단하면 안되기 때문에 네트워크, DB조작같은 일들은 반드시 별도의 스레드에서 수행을 해야한다. (background 또는 worker 스레드)

그러나 UI 스레드나 main 스레드를 제외한 다른 스레드에서는 UI를 업데이트가 불가능하다.

 

외부 thread에서 직접 ui 컴포넌트에 접근이 어렵기 때문에 외부 thread에서 UI thread 에 액세스하는 몇가지 방법이 제공된다.

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            // a potentially time consuming task
            final Bitmap bitmap =
                    processBitMap("image.png");
            imageView.post(new Runnable() {
                public void run() {
                    imageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

1) onClick에서 새로운 thread를 생성하고, 여기서 bitmap 관련 헤비한 작업을 수행하게한다.

2) 해당 작업이 끝나면, imageView에서 post를 하는 runnable은 ui thread안에서 수행되고, 이 ui thread안에서 imageView에 bitmap을 set해준다.

 

이렇게 구현을 하게되면 bitmap 작업은 별도의 스레드에서 수행되고, ImageView는 UI 스레드에서 조작되기 때문에 기대한대로 작업이 잘 수행된다.

 

하지만 코드가 복잡하다는 단점이있다.. 그렇기 때문에 이런 방법은 실제로는 잘 사용되지 않고, 보통은 Handler나 AsyncTask (지금은 디프리케이트됨)을 사용해서 UI와 상호작용해야 하는 background thread 구현을 좀더 간단하게- 직관적으로 구현할 수 있다.

 

AsyncTask 사용

AsyncTask를 extends하는 클래스를 정의해서 사용한다.

1) onPreExecute() : UI thread에서 doInBackground()실행전에 호출됨

2) doInBackground() : background thread에서 실행되는 작업 수행

3) onProgressUpdate(): doInBackground도는 중간중간에 UI thread에서 호출됨. progress bar 같은거 업데이트 할때 사용한다.

4) onPostExecute() : doInBackground가 끝나면 UI thread에서 onPostExecute가 실행되고, 여기서 UI를 업데이트 구현.

 

이제 UI 스레드에서 execute()를 호출하여 AsyncTask를 실행하면 된다.

 

* 단, 여러개의 AsyncTask를 동시에 실행시키기 위해서는 execute()로 하면 같은 스레드에서 실행되기 때문에 AsyncTask A 와 AsyncTask B가 동시에 실행되지 않고 순차적으로 실행되게 된다. 따라서 이런경우에는 execute() 대신 executeOnExecutor()를 사용해야 한다.

 

 private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
     // 백그라운드 작업
     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
             // Escape early if cancel() is called
             if (isCancelled()) break;
         }
         return totalSize;
     }
	
     // 중간중간 프로그레스 퍼센트를 업데이트 해준다.
     protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
     }
	
     // 작업이 모두 끝나면 Dialog를 띄워 준다.
     protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes");
     }
 }

 

Handler 사용

하나의 Handler는 하나의 thread와 연결되어있다. Handler를 만들면 이때 하나의 Looper 와 연결이 되고, Handler를 통해서 Looper의 메시지 큐에 message를 전달 해서 Looper의 thread에서 수행이 되게 된다.

handler를 사용해서 메시지를 보내는 방식으로 thread간에 통신을 쉽게 해준다.

 

 

사용 예는 Background thread에서 main thread의 looper를 가진 handler 객체에 메시지를 전달하면, main thread에서 message를 받아서 ui update작업을 수행하는데 많이 사용된다.

class HandlerSampleActivity : AppCompatActivity() {
    private lateinit var handlerButton: Button
    private lateinit var handlerTextView: TextView
    private lateinit var mainThreadHandler: Handler

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_handler)

        // 메인스레드에서 동작하는 handler 정의
        mainThreadHandler = object : Handler(Looper.getMainLooper()) {
            override fun handleMessage(msg: Message) {
                if (msg.what == 1) {
                    // UI 컴포넌트 업데이트 가능
                    handlerTextView.text = "Above button has been clicked."
                }
            }
        }
        handlerButton = findViewById<View>(R.id.handlerButton) as Button
        handlerButton.setOnClickListener(object : View.OnClickListener {
            override fun onClick(view: View?) {
                // 버튼 클릭시 background thread 생성해서 시작함
                val workerThread = WorkerThread()
                workerThread.start()
            }
        })
        handlerTextView = findViewById<View>(R.id.handlerTextView) as TextView
    }

    inner class WorkerThread : Thread() {
        override fun run() {
            // 백그라운드 스레드에서 메시지를 생성한다.
            val message = Message()
            message.what = 1

            // 메시지를 mainThreadHandler 에 전달한다.
            mainThreadHandler.sendMessage(message)
        }
    }
}

 

 

출처: android developer 사이트, https://www.dev2qa.com/android-handler-example/

 

728x90
반응형