Things take time

[Android] Async 비동기를 위한 기본적인 사용법 본문

Android(기능)

[Android] Async 비동기를 위한 기본적인 사용법

겸손할 겸 2017. 9. 1. 13:34

[AsyncTask]

 

흔히 쓰레드와 핸들러를 묶어서 해놓은 클래스라 생각하면 된다.

메인쓰레드의 경우에만 UI접근 및 수정이 가능하고, 이 메인쓰레드에게 개발자가원하는 UI를 변경하려면 핸들러를 사용해야한다.

 

그리고 네트워크나 비트맵같은 작업들은 메인쓰레드 하나에서 돌리기엔 프레임을 너무 잡아먹어 성능 저하의 원인이 되기 때문에 별도의 쓰레드를 만들어서, 각 쓰레드를 실행해야 한다. 이것들을 하나로 묶어서 편리하게 이용할 수 있는 개념이다.

 

 

[예제]

 

상황 : 앨범 파일의 경로 리스트를 받아와서 해당 사진들을 저화질로 만든다. 만드는 과정에는 프로그레스바를 통해 진행상황을 표기할 수 있도록 한다.

    public class CreateLowBitmap extends AsyncTask<Void, Integer, List>{

        List<String> imageList = new ArrayList<>();
        ProgressDialog mProgressDialog; // 진행 상태 다이얼로그
        String funcName = "";

        public CreateLowBitmap(List<String> imagePathList, String funcName) {
            this.imageList = imagePathList;
            this.funcName = funcName;
        }

        @Override
        protected void onPreExecute() {
            mProgressDialog = new ProgressDialog(mContext);
            mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            mProgressDialog.setMax(imageList.size());
            mProgressDialog.setTitle("파일 변환중입니다.");
            mProgressDialog.setCanceledOnTouchOutside(false);
            mProgressDialog.show();
        }

        @Override
        protected List<String> doInBackground(Void... params) {
            List<String> returnList = new ArrayList<>();

            for(int i=0; i<imageList.size(); i++) {
                final String path = imageList.get(i);

                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inJustDecodeBounds = true;
                Bitmap calculateBitmap = BitmapFactory.decodeFile(path, options);
                options.inSampleSize = calculateInSampleSize(options, 1024, 1024);
                options.inJustDecodeBounds = false;
                Bitmap bitmap = BitmapFactory.decodeFile(path, options);

                Random random = new Random();
                String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
                String imageFileName = timeStamp + "_" + random.nextInt(10000) + ".jpg";

                File fileCacheItem = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/test/" + imageFileName);

                if (fileCacheItem.exists()) {
                    Log.i("fileCacheItem 존재", fileCacheItem.getAbsolutePath());
                } else {
                    try {
                        Log.i("fileCacheItem 없음", "파일 상위 디렉토리 생성");
                        fileCacheItem.getParentFile().createNewFile();
                    } catch (Exception e) {
                        Log.e("path.mkdirs", e.toString());
                    }
                }
                OutputStream out = null;
                try {
                    fileCacheItem.createNewFile();
                    out = new FileOutputStream(fileCacheItem);
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
                } catch (Exception e) {
                    sendLogMsgPHP("createLowBitmapPath : " + e.toString());
                } finally {
                    try {
                        out.close();
                    } catch (IOException e) {
                        sendLogMsgPHP("createLowBitmapPath : " + e.toString());
                    }
                }
                Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
                Uri contentUri = Uri.fromFile(fileCacheItem);
                mediaScanIntent.setData(contentUri);
                mContext.sendBroadcast(mediaScanIntent);

                publishProgress(i);
                returnList.add(fileCacheItem.getAbsolutePath());
            }
            return returnList;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            mProgressDialog.setProgress(values[0]);
        }

        @Override
        protected void onPostExecute(List<String> strings) {
            if(mProgressDialog.isShowing()){
                mProgressDialog.dismiss();
            }
            UploadMultiFile uploadMultiFile = new UploadMultiFile(mContext, webView, funcName);
            uploadMultiFile.setInit(strings, "ImageFile");
            String url = baseDomain + "UploadFile4Multi.php";
            uploadMultiFile.execute(url);
        }
    }


 

[설명]

 

1) extends Async<Void, Integer, List<String>

- 첫 번째 파라미터는 이 UploadFile클래스 객체를 선언한 변수가 execute를 실행할때 전달되는 매개변수로, doInBackground에서 받아와 사용하는 매개 변수이다.
- 두 번째 파라미터는 onProgressUpdate의 매개변수이며, doInBackground에서 publishProgress()의 ()안에 들어갈 데이터 타입과 일치한다.
- 세 번째 파라미터는 doInBackground의 리턴 값이면서 onPostExecute의 매개변수이다.

 

2)

- doInBackground : AsyncTask 클래스를 사용하는 목적은, 메인 쓰레드(UI THREAD)이외의 개인 쓰레드를 사용하면서 다른 작업들(네트워크, 파일 저장 등)을 하기 위함이었는데, 이 작업을 하기 위한 공간이다. 즉, 사용자 지정 쓰레드와 같은 개념이면서.. 이  UploadFile이란 클래스를 객체로 만들어 사용할 변수의 execute()메소드로 호출되면, 그 execute()의 ()안 매개 변수 값이 전달된다. 내 예제에서는 서버에 올릴 것이므로 서버 URL을 전달받을 것이다. 이 함수에서는 UI접근이 불가능하다.

- onPreExecute : execute()함수를 통해 호출된 doInBackground보다 이전에 호출되는 메소드이다. 이 메소드에서는 UI에 대한 접근이 가능하다. 즉, doInBackground는 사용자 지정쓰레드이기에 메인쓰레드처럼 UI접근이 안되나, onPreExecute안에 UI접근 내용을 작성하면 메인쓰레드에서 이 부분에 있는 것을 인식하고, UI 작업을 가능하게 한다.

- onPostExecute : Post가 들어간 것으로 보니, execute 이후에 작업이 호출된다는 뜻이다. 이 곳은 doInBackground 메소드가 종료된 후 호출되는데, 만약 doInBackground가 정상적이지 않게 종료된 경우, 이 파라미터로 null 값이 전달된다. 그러므로 null이 들어왔을 때 처리하는 구문 등을 활용하면, 오류 예외 처리가 가능하다. 이 곳도 UI접근이 가능하다.

 

- onProgressUpdate : Async는 쓰레드기능과 핸들러기능을 동시에 수행할 수 있다고 했다. 근데 onPreExecute와 onPostExecute는 doInBackground  이 전, 이 후에만 UI접근이 가능한 것인데.. 만약 doInBackground 중간에 UI를 편집하고 싶다면 어떻게 해야할까. 그 작업을 이 메소드에서 수행한다. 이 메소드를 호출하려면 doInBackground 메소드내에 publishProgress(progress)를 호출하면 이 곳으로 온다! 이 곳도 UI접근이 가능하다.