짱짱해커가 되고 싶은 나

13-2. 스레드 본문

모바일

13-2. 스레드

동로시 2021. 2. 27. 19:55

* 프로그레스바 & 시크바

프로그레스바 : 작업의 진행 상태 확인용으로 자주 사용

시크바 : 음악/동영상 재생의 위치 지정용으로 자주 사용

 

package com.example.project13_1;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.Manifest;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.Switch;
import android.widget.TextView;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final ProgressBar pgBar = (ProgressBar)findViewById(R.id.progressBar);
        Button btn1 = (Button)findViewById(R.id.btn1);
        Button btn2 = (Button)findViewById(R.id.btn2);
        final TextView textView = (TextView)findViewById(R.id.tvSeek);
        SeekBar seekBar = (SeekBar)findViewById(R.id.seekBar);

        btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                pgBar.incrementProgressBy(10);
            }
        });

        btn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                pgBar.incrementProgressBy(-10);
            }
        });

        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                textView.setText("진행률 : " + progress + "%");
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });

    }
}

 

지금은 버튼을 클릭해서 프로그레스 바를 강제로 증감시켰는데 일반적으로는 다른 작업(ex.음악 재싱)을 하면서 프로그레스바가 자연스럽게 진행되도록 만든다. 이처럼 2개의 작업을 동시에 진행하기 위해서는 thread 가 필요하다.

 

* 스레드

여러 작업을 동시에 수행하기 위해 사용 (멀티 스레드/경량 스레드)

스레드는 함수와 달리 하나의 작업이 끝나기 전에 다른 작업을 동시에 진행시킬 수 있다.

 

ex) ProgressBar 2개가 있고 모두 max=100이라고 가정하자.

ProgressBar 1 은 10에서 2씩 증가하고, ProgressBar 2는 30에서 1씩 증가한다면 동시에 진행시킨다면 Bar 1이 먼저 100에 도착한다.

 

package com.example.project13_1;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.Manifest;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Environment;
import android.os.SystemClock;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.Switch;
import android.widget.TextView;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    ProgressBar pgBar1, pgBar2;
    Button btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        pgBar1 = (ProgressBar)findViewById(R.id.pgBar1);
        pgBar2 = (ProgressBar)findViewById(R.id.pgBar2);
        btn = (Button)findViewById(R.id.btn);

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(){
                    public void run(){
                        for(int i=pgBar1.getProgress(); i<100; i+=2){
                            pgBar1.setProgress(pgBar1.getProgress()+2);
                            SystemClock.sleep(100);
                        }
                    }
                }.start();

                new Thread(){
                    public void run(){
                        for(int i=pgBar2.getProgress(); i<100; i++){
                            pgBar2.setProgress(pgBar2.getProgress()+1);
                            SystemClock.sleep(100);
                        }
                    }
                }.start();
            }
        });
    }
}

 

* UI 스레드

UI 스레드는 화면의 위젯을 변경할 때 사용한다.

일반적인 스레드는 스레드 안에서 필요한 내용을 계산하는 것만 가능하며 화면의 다른 위젯을 변경할 수 없다.

 

ex) 프로게스바가 진행될 때마다 텍스트뷰에 진행률 표시

 

<일반 스레드 - 동작x>

package com.example.project13_1;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.Manifest;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Environment;
import android.os.SystemClock;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.Switch;
import android.widget.TextView;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    ProgressBar pgBar1, pgBar2;
    TextView textView1, textView2;
    Button btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        pgBar1 = (ProgressBar)findViewById(R.id.pgBar1);
        pgBar2 = (ProgressBar)findViewById(R.id.pgBar2);
        btn = (Button)findViewById(R.id.btn);
        textView1 = (TextView)findViewById(R.id.textView1) ;
        textView2 = (TextView)findViewById(R.id.textView2); 

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(){
                    public void run(){
                        for(int i=pgBar1.getProgress(); i<100; i+=2){
                            pgBar1.setProgress(pgBar1.getProgress()+2);
                            textView1.setText("1번 진행률 : " + pgBar1.getProgress() + "%");
                            SystemClock.sleep(100);
                        }
                    }
                }.start();

                new Thread(){
                    public void run(){
                        for(int i=pgBar2.getProgress(); i<100; i++){
                            pgBar2.setProgress(pgBar2.getProgress()+1);
                            textView2.setText("2번 진행률 : " + pgBar2.getProgress() + "%");
                            SystemClock.sleep(100);
                        }
                    }
                }.start();
            }
        });
    }
}

앞에서 했던 그냥 스레드에서 TextView 를 변경하려고 했기 때문에 에러가 발생한다.

(예외적으로 프로그레스바와 그 자식 위젯은 스레드 안에서 변경이 가능하다.)

 

<UI 스레드 - 동작 o>

package com.example.project13_1;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.Manifest;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Environment;
import android.os.SystemClock;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.Switch;
import android.widget.TextView;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    ProgressBar pgBar1, pgBar2;
    TextView textView1, textView2;
    Button btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        pgBar1 = (ProgressBar)findViewById(R.id.pgBar1);
        pgBar2 = (ProgressBar)findViewById(R.id.pgBar2);
        btn = (Button)findViewById(R.id.btn);
        textView1 = (TextView)findViewById(R.id.textView1) ;
        textView2 = (TextView)findViewById(R.id.textView2);

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(){
                    public void run(){
                        for(int i=pgBar1.getProgress(); i<100; i+=2){
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    pgBar1.setProgress(pgBar1.getProgress()+2);
                                    textView1.setText("1번 진행률 : " + pgBar1.getProgress() + "%");
                                }
                            });
                            SystemClock.sleep(100);
                        }
                    }
                }.start();

                new Thread(){
                    public void run(){
                        for(int i=pgBar2.getProgress(); i<100; i++){
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    pgBar2.setProgress(pgBar2.getProgress()+1);
                                    textView2.setText("2번 진행률 : " + pgBar2.getProgress() + "%");
                                }
                            });
                            SystemClock.sleep(100);
                        }
                    }
                }.start();
            }
        });
    }
}

runOnUiThread(new Runnable) 안에 위젯을 변경하는 코드를 넣으면 된다.

 

프로젝트1: MP3 플레이어 스레드 추가

TextView 1개 추가 - 현재 재생 중인 노래 시간

 

package com.example.project13_1;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.Manifest;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Environment;
import android.os.SystemClock;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.Switch;
import android.widget.TextView;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    ListView listView;
    Button btn1, btn2;
    TextView textView, textView2;
    ProgressBar pgMP3;

    ArrayList<String> mp3List;
    String selectedMP3;

    String path =  "/sdcard/Music/";
    MediaPlayer mPlayer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setTitle("간단 MP3 플레이어");

        ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},
                MODE_PRIVATE);

        mp3List = new ArrayList<String>();

        File[] listFiles = new File("/sdcard/Music/").listFiles();
        String fileName, extName;

        for(int i=0; i<listFiles.length; i++){
            fileName = listFiles[i].getName();
            extName = fileName.substring(fileName.length() - 3);
            if(extName.equals((String)"mp3")){
                mp3List.add(fileName);
            }
        }

        listView = (ListView)findViewById(R.id.listView);
        btn1 = (Button)findViewById(R.id.btn1);
        btn2 = (Button)findViewById(R.id.btn2);
        textView = (TextView)findViewById(R.id.textView);
        textView2 = (TextView)findViewById(R.id.textView2);
        pgMP3 = (ProgressBar)findViewById(R.id.pbMP3);

        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_single_choice, mp3List);
        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
        listView.setAdapter(adapter);
        listView.setItemChecked(0, true);

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                selectedMP3 = mp3List.get(position);
            }
        });

        selectedMP3 = mp3List.get(0);

        btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try{
                    mPlayer = new MediaPlayer();
                    mPlayer.setDataSource(path + selectedMP3);
                    mPlayer.prepare();
                    mPlayer.start();
                    btn1.setClickable(false);
                    btn2.setClickable(true);
                    textView.setText("실행중인 음악 : " + selectedMP3);
                    new Thread() {
                        SimpleDateFormat timeFormat = new SimpleDateFormat(" mm:ss ");

                        public void run() {
                            if (mPlayer == null) return;
                            pgMP3.setMax(mPlayer.getDuration());
                            while (mPlayer.isPlaying()) {
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        pgMP3.setProgress(mPlayer.getCurrentPosition());
                                        textView2.setText("진행 시간 : " + timeFormat.format(mPlayer.getCurrentPosition()));
                                    }
                                });
                                SystemClock.sleep(200);
                            }
                        }
                    }.start();
                } catch(IOException e){

                }
            }
        });

        btn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mPlayer.stop();
                mPlayer.reset();
                btn1.setClickable(true);
                btn2.setClickable(false);
                textView.setText("실행중인 음악 : ");
                textView2.setText("진행 시간 : ");
                pgMP3.setProgress(0);
            }
        });
        btn2.setClickable(false);

    }
}

프로젝트 1

 

프로젝트2: MP3 플레이어 시크바 추가

시크바로 이동한 지점의 음악 자동 재생

 

	pgMP3.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if(fromUser){
                    mPlayer.seekTo(progress);
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });

 

프로젝트3: MP3 플레이어 Handler 객체 이용

UI 작업을 비동기로 처리하다 보면 동기화 문제가 발생할 수 있다. 따라서 안드로이드는 이런 문제를 해결하고자 병렬로 동작하는 메인 스레드와 워커 스레드 사이에 핸들러를 두고 UI 작업은 모두 메인 스레드로 전달되도록 한다.

 

package com.example.project13_1;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.Manifest;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.Switch;
import android.widget.TextView;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    ListView listView;
    Button btn1, btn2, btn3;
    TextView textView, textView2;
    SeekBar pgMP3;

    ArrayList<String> mp3List;
    String selectedMP3;
    int position;

    String path =  "/sdcard/Music/";
    MediaPlayer mPlayer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setTitle("간단 MP3 플레이어");

        ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},
                MODE_PRIVATE);

        mp3List = new ArrayList<String>();

        File[] listFiles = new File("/sdcard/Music/").listFiles();
        String fileName, extName;

        for(int i=0; i<listFiles.length; i++){
            fileName = listFiles[i].getName();
            extName = fileName.substring(fileName.length() - 3);
            if(extName.equals((String)"mp3")){
                mp3List.add(fileName);
            }
        }

        listView = (ListView)findViewById(R.id.listView);
        btn1 = (Button)findViewById(R.id.btn1);
        btn2 = (Button)findViewById(R.id.btn2);
        btn3 = (Button)findViewById(R.id.btn3);
        textView = (TextView)findViewById(R.id.textView);
        textView2 = (TextView)findViewById(R.id.textView2);
        pgMP3 = (SeekBar)findViewById(R.id.pbMP3);

        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_single_choice, mp3List);
        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
        listView.setAdapter(adapter);
        listView.setItemChecked(0, true);

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                selectedMP3 = mp3List.get(position);
            }
        });

        selectedMP3 = mp3List.get(0);

        Handler mp3Handler = new Handler(){
            @Override
            public void handleMessage(@NonNull Message msg) {
                SimpleDateFormat timeFormat = new SimpleDateFormat(" mm:ss ");
                pgMP3.setProgress(mPlayer.getCurrentPosition());
                textView2.setText("진행 시간 : " + timeFormat.format(mPlayer.getCurrentPosition()));
            }
        };

        pgMP3.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if(fromUser){
                    mPlayer.seekTo(progress);
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });

        btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    mPlayer = new MediaPlayer();
                    mPlayer.setDataSource(path + selectedMP3);
                    mPlayer.prepare();
                    mPlayer.seekTo(position);
                    mPlayer.start();
                    
                    btn1.setClickable(false);
                    btn2.setClickable(true);
                    btn3.setClickable(true);
                    textView.setText("실행중인 음악 : " + selectedMP3);

                    new Thread() {
                        public void run() {
                            if (mPlayer == null) return;
                            pgMP3.setMax(mPlayer.getDuration());
                            while (mPlayer.isPlaying()) {
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        mp3Handler.sendEmptyMessage(0);
                                    }
                                });
                                SystemClock.sleep(200);
                            }
                        }
                    }.start();

                } catch (IOException e) {
                }

            }
        });

        btn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mPlayer.pause();
                position = mPlayer.getCurrentPosition();
                btn1.setClickable(true);
                btn2.setClickable(false);
                btn3.setClickable(true);
            }
        });

        btn3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mPlayer.stop();
                mPlayer.reset();
                btn1.setClickable(true);
                btn2.setClickable(false);
                btn3.setClickable(false);
                textView.setText("실행중인 음악 : ");
                textView2.setText("진행 시간 : ");
                position = 0;
                pgMP3.setProgress(0);
            }
        });
        btn2.setClickable(false);
        btn3.setClickable(false);

    }
}

'모바일' 카테고리의 다른 글

14-1. 서비스  (0) 2021.02.27
13-3. 구글 지도  (0) 2021.02.27
13-1. 오디오  (0) 2021.02.27
12-2. SQLite 연습  (0) 2021.02.26
12-1. SQLite  (0) 2021.02.26
Comments