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

이것도 스레드와 관련이 있을까요..? (함수호출 순서 및 시점)

0 추천

프래그먼트의 onCreateView 내에서 실행했을때와 멤버함수에서 실행했을때 실행되고 안되고 차이가 있어 질문좀 드립니다.

pager.setOffscreenPageLimit(items.size());

문제의 코드는 이것이고

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.bstcproject, PID: 24791
    java.lang.IllegalArgumentException: Offscreen page limit must be OFFSCREEN_PAGE_LIMIT_DEFAULT or a number > 0
        at androidx.viewpager2.widget.ViewPager2.setOffscreenPageLimit(ViewPager2.java:833)
        at com.example.bstcproject.MovieFragment.onCreateView(MovieFragment.java:125)
        at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2698)
        at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:320)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1187)
        at androidx.fragment.app.FragmentManager.addAddedFragments(FragmentManager.java:2224)

대충 이러한 에러가 납니다

 

프래그먼트의 onCreateView입니다.

public View onCreateView(final LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        final ViewGroup rootView =(ViewGroup) inflater.inflate(R.layout.fragment_movie, container, false);

        items = new ArrayList<>();
        pager = rootView.findViewById(R.id.pager);
        adpater = new MovieListAdapter();

        // 서버큐 연결
        if (AppHelper.requestQueue == null) {
            AppHelper.requestQueue = Volley.newRequestQueue(getContext());
        }
        requestMovieList(); // 서버요청하기

        pager.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
        pager.setAdapter(adpater);

        // 뷰페이저 좌우 프리뷰 구현1
        pager.setClipToPadding(false);
        pager.setClipChildren(false);
        pager.setOffscreenPageLimit(items.size());     pager.getChildAt(0).setOverScrollMode(RecyclerView.OVER_SCROLL_NEVER);

        // 뷰페이저 좌우 프리뷰 구현2
        CompositePageTransformer compositePageTransformer = new CompositePageTransformer();
        compositePageTransformer.addTransformer(new MarginPageTransformer(0));

        // 페이지 넘기는 애니메이션? 같은 효과를 주는 코드인듯
        compositePageTransformer.addTransformer(new ViewPager2.PageTransformer() {
            @Override
            public void transformPage(@NonNull View page, float position) {
                float r = 1 - Math.abs(position);
                page.setScaleY(0.85f + r * 0.15f);

            }
        });
        pager.setPageTransformer(compositePageTransformer);
        return rootView;
    }

해당 코드가 onCreateView의 해당 위치에 있을때 에러가나고,

// 요청 객체만들기
    public void requestMovieList() {
//        http://boostcourse-appapi.connect.or.kr:10000/movie/readMovieList?type=1
        String url = "http://" + AppHelper.host + ":"+ AppHelper.port + "/movie/readMovieList";
        url += "?" + "type=" + 1; // 요청 파라미터

        //요청객체
        StringRequest request = new StringRequest(
                Request.Method.GET,
                url,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        // 요청 보내고 정상 응답을 받는곳
                        println(response);
                        processResponse(response); // gson으로 파싱하기
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        // 에러 발생시 호출 됨
                        println("에러 발생 : " + error );
                    }
                }
        );

        request.setShouldCache(false);
        AppHelper.requestQueue.add(request); // 요청큐에 요청객체 넣기
    }

    // 받아온 응답 gson으로 자바 객체화 (파싱)
    public void processResponse(String response) {
        Gson gson = new Gson();

        ResponseInfo info = gson.fromJson(response, ResponseInfo.class);
        if(info.code == 200) {
            movie = gson.fromJson(response, Movie.class);


            // MovieInfo 정보를 하나씩 꺼내와 ArrayList에 추가
            for(int i=0; i<movie.result.size(); i++) {
                mvmitem = movie.result.get(i);
                items.add(mvmitem);
            }
            adpater.setItems(items); // ArrayList<MovieModel>을 통째로 전달
            adpater.notifyDataSetChanged();
//            pager.setOffscreenPageLimit(items.size());
        }
    }

processResponse 메소드 안의 주석처리된곳에서 실행하면 정상작동 됩니다.

그런데 이것도 파라미터로 items.size()를 줬을때 얘기고

onCreateView에서 실행해도 파라미터로 그냥 int 값인 뭐 3이나 4 등등을 줬을때는 정상작동 합니다.

하지만 무작정 int값을 주기보다는 ArrayLits의 아이템사이즈에 맞춰서 주고싶어서 저렇게 넣었습니다.

같은코드인데

 

onCreateView에서 순서를 보시면 제가 기대하기를

그냥 코드 순서대로 위에서 아래로 순차적으로 실행되기를 기대했습니다

requestMovieList()가 호출되고 그안에서 이제 콜백메소드안에서 processResponse()가 호출됩니다.

이후에 processResponse()내의 코드를 순차적으로 실행되고 종료되고 requestMovieList()도 종료되고

이제 남은 onCreateView()내의 requestMovieList()이후의 코드들을 순차실행 되기를 기대했는데

그렇지 않은것 같습니다...

코드를 대거 변경하기 전 코드인데, 이때는 코드가 저위치에 있는데도 실행이 됐습니다

그런데 지금 processResponse함수내로 들어갔을때는 되고 지금은 안되는걸보면

requestMovieList()를 호출하고 함수내에서 processResponse() 호출할때 이 함수가 끝나기전에

혹시 onCreateView() 에서 나머지 코드가 실행돼서 그런것 아닌가하는생각이 들기때문입니다..

 

이게 가능한가요? 원래 제 어줍짢은 지식으로는

함수를 호출해서 진입하면 함수가 다끝나고 다음 코드가 실행되는것 아닌가요?

이렇게 함수가 진행되고 있는데 다음코드가 진행되고 그런건 스레드가 여러개 일때나 막 그렇게 실행되는 

것 아닌가요?..제 코드는 스레드를 따로 만들지 않았고 메인스레드 하나에서 진행되는 것일텐데..

그래서 제가 추측하기로는

onCreateView()에서 setOffscreenPageLimit() 실행했을시 -> onCreateView에서 나머지 코드가 실행되어 processResponse가 끝나기전에  setOffscreenPageLimit()가 실행되어 에러가 난다.

 

processResponse() 내에서 setOffscreenPageLimit()  실행했을시 -> processResponse내에서 setItems()까지 다 끝내고 나서야 setOffscreenPageLimit()이 실행되므로 onCreateView()가 진행을 해도 에러가 나지않는다.

 

같은데.. 제가 생각한 추측이 맞나요..? 순차적으로 함수가 다끝나고 실행되는것이아니라

함수가 호출되고 있는 와중에도 무시하고 onCreateView()내에서 막 다음 코드가 진행되고 그런건가요..?

아니면 혹시 또다른 이유가 있을까요?

codeslave (3,940 포인트) 님이 2020년 11월 6일 질문
codeslave님이 2020년 11월 6일 수정

1개의 답변

0 추천

 혹시 sync와 async의 차이점을 아시는지요? 이건 IOS든 안드로이드이든 상관없이 모든 프로그램밍에 공통된다고 보시면되는데요. 쓰레드에는 메인 쓰레드랑 백그라운드 쓰레드가 있습니다. 메인쓰레드는 보통 화면을 그려주기 위해 사용됩니다. 안드로이드폰에서는 매초마다 60프레임을 그리도록 되어 있습니다. 따라서 메인쓰레드는 화면 그리는데만도 상당히 바쁘기 때문에, 여타의 작업은 백그라운드 쓰레드에서 진행해 주어야 합니다. 이 때문에 네트워크 작업을 할 때는 동기가 아닌 비동기로 작업을 하게 됩니다. 왜냐하면 해당 작업이 끝날 때까지 UI를 블락할 수가 없기 때문이죠.따라서 네트워크 작업을 수행하고 결과를 받는 것은 콜백을 통해서 하게 됩니다. 이게 AsyncTask를 쓸 때 쓰는 것과 같은 패턴입니다. 

따라서 님의 추측대로 requestMovieList()는 백그라운드 작업이기 때문에 이 작업 후에는 뷰페이저의 미리보기를 아이템을 세팅하더라도 동작을 할지 보장할 수 없습니다. requestMovieList()에서 결과를 받는 부분이 있습니다. 여기에 뷰페이져의 미리보기를 세팅하는 코드를 위치시켜셔야 합니다.

spark (226,420 포인트) 님이 2020년 11월 6일 답변
명쾌한답변 감사합니다! 스레드와 관련된일이었군요.
그런데 한가지만 더 여쭙겠습니다.
그럼 settOffscreenPageLimit()뿐만아니라 본문 코드상에서 주석 좌우 프리뷰 1,2의 코드들 모두 requestMovlieList()의 결과받는 콜백함수인 onResponse로 위치시켜주는게ㅜ좋을까요?
콜백으로 받아서 처리하는 데이터와 관련된 부분은 모두 콜백으로 옮기세요. settOffscreenPageLimit과 좌우 프리뷰 1,2 들도 콜백에서 데이터를 받아야 하니까 옮기시는 게 맞을 것 같네요. 그렇지 않는 것들은 그냥 놔두시는게 나을 것 같구요.
감사합니다 수정하겠습니다
...