마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

AsyncTask 비동기 // 네트워크 속도 - 코드 속도 차이

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

    new TestTask().execute();  //실행
public class TestTask extends AsyncTask {
    @Override
    protected void onPreExecute() {  //작업 시작전 동작
        Log.d("ASD", "onPreExecute");
        if (queue == null) {
            queue = Volley.newRequestQueue(getApplicationContext());
        }
    }
    @Override
    protected Object doInBackground(Object[] objects) {  //백그라운드에서 실행
        Log.d("ASD", "doInBackground");

        RequestString();

        publishProgress();  //중간 UI업데이트가 필요하면 호출
        return null;
    }
    @Override
    protected void onProgressUpdate(Object[] values) {  //publishProgress() 호출될떄 실행
        Log.d("ASD", "onProgressUpdate");
        aFragment.atv.setText(data.getTitle());
        Log.d("ASD", "onProgressUpdate : "+data.getTitle());
    }
    @Override
    protected void onPostExecute(Object o) { //결과를 리턴하며 끝났을때 동작작            
        Log.d("ASD", "onPostExecute");
    }
}
private void RequestString() {
    StringRequest request = new StringRequest(url, new Response.Listener<String>() {
        @Override
        public void onResponse(String response) {
            try {
                JSONObject jsonObject = new JSONObject(response);
                data.setTitle(jsonObject.getString("title"));
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            Log.d("ASD", error.toString());
        }
    });
    request.setShouldCache(false);
    queue.add(request);
}
2021-11-04 18:52:D/ASD: onPreExecute
2021-11-04 18:52:D/ASD: doInBackground
2021-11-04 18:52:D/ASD: onProgressUpdate
2021-11-04 18:52:D/ASD: onProgressUpdate : null
2021-11-04 18:52:D/ASD: onPostExecute

쓰레드, 핸들러, AsyncTask 다 써보면서 공부중인데 네트워크 정달 속도 보다 

get으로 가져오는 코드 속도가 더빠른 현상은 도대체 어뜩해 처리를 해야하나요...?

저것을 다른 프래그먼트에서 가져올려하는데 속도차이 때문에 해결이 안됨니다...ㅠ

dkssudgktpdy (220 포인트) 님이 2021년 12월 1일 질문
dkssudgktpdy님이 2021년 12월 7일 reshown
질문에 필요한 정보들이 빠져있습니다. get이 AsyncTask.get을 말하는 건지 다른 건지, get이 빠르다는 것은 어떤 의미이고 어떻게 확인을 하셨는지, 그리고 프레그먼트에서 어떻게 처리를 하셨는데 문제가 되는 부분이 뭔지 등등요.

1개의 답변

0 추천
 
채택된 답변

AsyncTask나 Volley 모두 callback을 이용하여 응답을 처리합니다. 질문을 다시 읽어보니 콜백에 대해 좀 더 설명을 드리는게 좋을 것 같네요. 콜백은 이미 다른데서 사용하고 계실 건데, 눈여겨 보지 않으셨을 겁니다.

button.setOnClickListener(new View.OnClickListener(){...});

이렇게 버튼에 View.OnClickListener를 설정해서 사용해보셨을 겁니다. 이게 콜백입니다. 화면에 있는 버튼을 터치할 때 안드로이드 시스템이 센서를 통해 제일먼저 감지를 하죠. 그 다음에 버튼이 눌린 앱에게 어떻게 처리할 지를 물어보게 됩니다. 이 때 View.OnClickListener가 등록되어 있다면 그 안에 있는 onClick 메소드를 실행시켜 주는 겁니다. 다만 버튼 클릭은 화면에서 일어나는 이벤트이므로 네트워크 호출과는 다르게 바로 반응을 하도록 설계가 되어 있습니다. 그리고 이건 메인쓰레드라고 하는 화면업데이트를 전담하는 쓰레드에서 동작을 합니다. 메인쓰레드에서 실행되는 동작들은 즉각적인 반응이 필요한 것들이라고 보시면 됩니다. 반면 네트워크 호출은 인터넷이나 서버의 상황에 따라 한참 대기할 수 있기 때문에 메인쓰레드에서 처리할 수가 없고 백그라운드 쓰레드에서 처리함으로써 사용자 화면에 영향을 주지 않도록 설계가 되어 있습니다.

AsyncTask, Volley의 사용법은 버튼 클릭과 마찬가지로 리스너를 등록해 주고 리스너 안의 메소드들을 구현해주면 됩니다. 네트워크 호출의 이벤트에 따라 적합한 메소드가 호출되게 됩니다.

알아 두셔야 할 것은 AsyncTask는 안드로이드 11부터 deprecated되어서 사용하지 않으시는게 좋습니다. 어차피 내년 하반기 까지 Play Store에 있는 앱들은 안드로이드 11로 업데이트를 해야하므로, 지금이라고 사용하지 않으시는게 좋습니다. 그리고 Volley는 AsyncTask가 필요없이 리스너만 등록하면 백그라운드와 메인쓰레드에 대한 처리를 할 수 있도록 되어 있습니다. 따라서 님의 코드에서 AsyncTask부분은 불필요한 부분입니다.

다만 하실일은 리스너를 등록해주는 겁니다. 님이 사용하려고 하는 프레그먼트에서 리스너를 등록해주고 필요한 메소드를 구현해 주면 됩니다. 

그리고 아래의 개발자 문서에 Volley Request를 거의 모든 상황에 사용할 수 있도록 Gson을 이용해 만들어놓은 예제가 있습니다. 참고하세요.
https://developer.android.com/training/volley/request-custom#example:-gsonrequest

간단하게 JSONObject를 이용한 클래스를 보여드리면(바로 가져다 쓰지 마시고, 어떻게 일반화 시키는지만 보세요.)

public class VolleyTask {

    interface Listener {
        void onVolleyResponse(JSONObject jsonObject);

        void onVolleyError(VolleyError error);
    }

    private String url;
    private File cacheDir;
    private JSONObject request;
    private VolleyTask.Listener listener;

    private VolleyTask(String url) {
        this.url = url;
    }

    public static VolleyTask with(String url) {
        return new VolleyTask(url);
    }

    public VolleyTask cacheDir(File dir) {
        this.cacheDir = dir;
        return this;
    }

    public VolleyTask setRequest(JSONObject request) {
        this.request = request;
        return this;
    }

    public VolleyTask setListener(Listener listener) {
        this.listener = listener;
        return this;
    }

    private Cache getCache() {
        return new DiskBasedCache(cacheDir, 1024 * 1024); // 1MB cap
    }


    private RequestQueue startRequest() {
        RequestQueue requestQueue;
        // Instantiate the cache
        // Set up the network to use HttpURLConnection as the HTTP client.
        Network network = new BasicNetwork(new HurlStack());
        // Instantiate the RequestQueue with the cache and network.
        requestQueue = new RequestQueue(getCache(), network);
        // Start the queue
        requestQueue.start();
        return requestQueue;
    }

    public void get() {
        addToQueue(Request.Method.GET);
    }

    public void post() {
        addToQueue(Request.Method.POST);
    }
    
    private void addToQueue(int method) {
        RequestQueue requestQueue = startRequest();
        JsonObjectRequest jsonRequest = jsonObjectRequest(method);
        requestQueue.add(jsonRequest);
    }

    private JsonObjectRequest jsonObjectRequest(int method) {
        return new JsonObjectRequest(method, url, request, new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                listener.onVolleyResponse(response);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                listener.onVolleyError(error);
            }
        });
    }
}

 

위의 클래스를 아래와 같이 호출합니다.

File cacheDir = context.getCacheDir()
JSONObject jsonObject = new JSONObject();
VolleyTask.with("http://www.example.com")
    .cacheDir(cacheDir)
    .setRequest(jsonObject)
    .setListener(new VolleyTask.Listener() {
                    @Override
                    public void onVolleyResponse(JSONObject jsonObject) {
                          // 응답처리
                    }

                    @Override
                    public void onVolleyError(VolleyError error) {
                        //에러 처리
                    }
                })
    .post();

위에서 언급한 GsonRequest 조합해서 사용하다면 좀 더 강력한 방법이 될 것 같습니다. 한번 시도해 보세요.

그리고 여전히 Retrofit이 좀 더 유연한 옵션이긴 합니다.

spark (105,040 포인트) 님이 2021년 12월 2일 답변
dkssudgktpdy님이 2021년 12월 7일 채택됨
먼저 길게 답변 주셔서 감사드립니다!!
Volley, JSONObject는 좀 더 공부해서 Retrofit, GsonRequest로 유연하게 사용해 보겠습니다!!
아직 정확히 이해가 안되서 다시 한번 질문드립니다 ㅠㅠ
말씀하신대로 버튼을 눌러 화면 업데이트는 메인쓰레드, 네트워크 호출을 위한것은 백그라운드에서 진행되는것은 확인하였습니다.
제가 헷갈려서 상황을 가정해보겠습니다.
값을 1씩 더해주는 버튼이 있고, 백그라운드 쓰레드로 서버에 저장되어있는 값 '100' 을 가져온다는 상황에서 백그라운드가 실행이 끝나기전 ( 값을 가져오기전 ) 버튼을 누르면 값이 없어서 Null값또는 오류가 뜨는 상황을 해결하고 싶습니다. 이것을 코드 속도랑 네트워크 속도차이로 본것입니다. 아직 콜백, 리스너 개념이 잡히질 않았는데 이것을 해결하는게 맞나요??
위에 질문도 풀어서 설명하자면
data.setTitle("Title") 로 data에 값을 넣고 getTitle()로 넣은 값을 가져오기 위함이였는데 setTitle()로 값이 들어가기 전에 getTitle이 실행되어서 Log에 null이 뜬것을 보여드린겁니다 ㅠㅠ
앞서 말씀드렸듯이 AsyncTask나 Volley에서와 같이 콜백을 사용할 때는 콜백 안에서 뷰에 대한 제어가 이루어지도록 하시면 됩니다. 아래와 같이 하시면 됩니다.

// 서버에서 타이틀을 가져옴
public void fetchTitleFromServer() {
new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
               // 서버에서 타이틀을 받았을 때 텍스트뷰에 설정.
               parseJSONResponse();
               textView.setTitle("서버에서 받은 타이틀");
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                showErrorMessage();
            }
        });
);

// 버튼 클릭 동작
button.setOnClickListener(View.OnClickListener() {
    @Override
    public void onClick(View view) {
         fetchTitleFromServer();
    }
});

이래야 비동기적으로 동작할 수가 있습니다. 그리고 실제 업무에서는 서버에서 데이터를 가져오는 사이에 ProgressBar를 보여주거나 서버에서 데이터를 가져오고 있다는 것을 사용자가 알 수 있도록 화면을 업데이트해줍니다. 텍스트뷰에는 텍스트를 서버에서 가져오기 전에는 적당한 텍스트를 보여주시면 되겠죠. 예를 들면 빈 문자열이라던가 "로딩중...." 이런 식으로요.
...