[android] FragmentPagerAdapter를 사용할 때 기존 조각을 가져 오는 방법

나는 탭 관리를 구현하는 도우미 클래스 와 관련된 모든 세부 정보를 Activity사용하는을 통해 서로 통신하는 내 조각을 만드는 데 문제 가 있습니다 . Android 샘플 프로젝트 Support4Demos 에서 제공하는 것과 똑같이 구현 했습니다 .FragmentPagerAdapterViewPagerTabHostFragmentPagerAdapter

주요 질문은 FragmentManagerId 나 Tag가없는 경우 어떻게 특정 조각을 얻을 수 있습니까? FragmentPagerAdapter조각을 만들고 ID와 태그를 자동으로 생성합니다.



답변

문제 요약

참고 :이 답변에서는 FragmentPagerAdapter소스 코드 를 참조하겠습니다 . 그러나 일반적인 솔루션은 FragmentStatePagerAdapter.

당신이 당신이 아마 이미 알고 읽는 경우 FragmentPagerAdapter/ FragmentStatePagerAdapter생성하기위한 것입니다 Fragments당신을 위해 ViewPager,하지만 활동 휴양에 따라 이러한 (앱이 메모리를 회복 죽이는 장치 회전 또는 시스템에서 여부를) Fragments다시 생성되지 않습니다, 대신 자신을 에서 검색된 인스턴스FragmentManager . 이제 작업을 수행 Activity하기 위해 이들 Fragments에 대한 참조가 필요 하다고 말하십시오 . 내부적으로 설정 했기 때문에 id또는 tagfor이 생성 되지 않았습니다 . 그래서 문제는 어떻게 그 정보없이 그들에 대한 참조를 얻는 것입니다 …FragmentsFragmentPagerAdapter

현재 솔루션의 문제점 : 내부 코드에 의존

이 질문과 유사한 질문에 대해 본 많은 솔루션 은 내부적으로 생성 된 태그를Fragment 호출 FragmentManager.findFragmentByTag()하고 모방하여 기존 에 대한 참조를 얻는 데 의존합니다 .. 이것의 문제는 당신이 내부 소스 코드에 의존하고 있다는 것입니다. 우리 모두가 알고 있듯이 영원히 동일하게 유지된다는 보장은 없습니다. Google의 Android 엔지니어 는 기존 .NET에 대한 참조를 찾을 수 없도록 코드를 손상 시키는 구조를 쉽게 변경할 수 있습니다 ."android:switcher:" + viewId + ":" + idtagFragments

내부에 의존하지 않는 대체 솔루션 tag

다음 은에 대한 내부 집합에 의존하지 않는 에서 Fragments반환 된에 대한 참조를 가져 오는 방법에 대한 간단한 예입니다 . 핵심은 에서 대신 참조 를 재정의 하고 저장하는 입니다 .FragmentPagerAdaptertagsFragmentsinstantiateItem()getItem()

public class SomeActivity extends Activity {
    private FragmentA m1stFragment;
    private FragmentB m2ndFragment;

    // other code in your Activity...

    private class CustomPagerAdapter extends FragmentPagerAdapter {
        // other code in your custom FragmentPagerAdapter...

        public CustomPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            // Do NOT try to save references to the Fragments in getItem(),
            // because getItem() is not always called. If the Fragment
            // was already created then it will be retrieved from the FragmentManger
            // and not here (i.e. getItem() won't be called again).
            switch (position) {
                case 0:
                    return new FragmentA();
                case 1:
                    return new FragmentB();
                default:
                    // This should never happen. Always account for each position above
                    return null;
            }
        }

        // Here we can finally safely save a reference to the created
        // Fragment, no matter where it came from (either getItem() or
        // FragmentManger). Simply save the returned Fragment from
        // super.instantiateItem() into an appropriate reference depending
        // on the ViewPager position.
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
            // save the appropriate reference depending on position
            switch (position) {
                case 0:
                    m1stFragment = (FragmentA) createdFragment;
                    break;
                case 1:
                    m2ndFragment = (FragmentB) createdFragment;
                    break;
            }
            return createdFragment;
        }
    }

    public void someMethod() {
        // do work on the referenced Fragments, but first check if they
        // even exist yet, otherwise you'll get an NPE.

        if (m1stFragment != null) {
            // m1stFragment.doWork();
        }

        if (m2ndFragment != null) {
            // m2ndFragment.doSomeWorkToo();
        }
    }
}

또는 당신이 작업하는 것을 선호하는 경우 tags클래스 멤버 변수 대신 /를 참조 Fragments당신은 또한 잡을 수 tags에 의해 세트를 FragmentPagerAdapter같은 방식으로 참고 :이 적용되지 않습니다 FragmentStatePagerAdapter설정되지 않기 때문에 tags그것을 만들 때 Fragments.

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
    // get the tags set by FragmentPagerAdapter
    switch (position) {
        case 0:
            String firstTag = createdFragment.getTag();
            break;
        case 1:
            String secondTag = createdFragment.getTag();
            break;
    }
    // ... save the tags somewhere so you can reference them later
    return createdFragment;
}

이 메서드는 내부 tag설정 을 모방하는 데 의존하지 FragmentPagerAdapter않고 대신 적절한 API를 사용하여 검색합니다. 이렇게하면 tag향후 버전의 변경 사항 SupportLibrary이 여전히 안전 할 것입니다.


잊지 마십시오 당신의 디자인에 따라 것을 ActivityFragments당신이 일에 작업을 시도하고 또는 당신이 그렇게함으로써 그것을 위해 계정에 아직 존재하지 않을 수 있습니다 null귀하의 참조를 사용하기 전에 검사를.

또한 대신 을 사용하는 경우FragmentStatePagerAdapter 하드 참조 Fragments가 많을 수 있고 하드 참조가 불필요하게 메모리에 유지 될 수 있으므로 하드 참조를 유지하고 싶지 않습니다 . 대신 표준 Fragment참조 WeakReference대신 변수에 참조를 저장하십시오 . 이렇게 :

WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
    // reference hasn't been cleared yet; do work...
}


답변

다음 게시물을 기반으로 내 질문에 대한 답변을 찾았습니다. fragmentpageradapter에서 조각 재사용

내가 배운 몇 가지 :

  1. getItem(int position)에서 FragmentPagerAdapter이 방법이 실제로 무엇을하는지 오히려 오해의 소지가 이름입니다. 기존 조각을 반환하지 않고 새 조각을 만듭니다. 따라서 메서드의 이름을 createItem(int position)Android SDK 와 같은 이름으로 변경해야합니다 . 따라서이 방법은 조각을 얻는 데 도움이되지 않습니다.
  2. 포스트 지원 FragmentPagerAdapterholds의 설명에 따라 이전 조각에 대한 참조를 유지 해야 FragmentPagerAdapter합니다. 따라서 조각 또는 해당 태그에 대한 참조가 없음을 의미합니다. 조각 태그가있는 경우를 FragmentManager호출 하여에서 쉽게 참조를 검색 할 수 있습니다 findFragmentByTag(). 주어진 페이지 위치에서 조각의 태그를 찾는 방법이 필요합니다.

해결책

클래스에 다음 도우미 메서드를 추가하여 조각 태그를 검색하고 findFragmentByTag()메서드로 보냅니다 .

private String getFragmentTag(int viewPagerId, int fragmentPosition)
{
     return "android:switcher:" + viewPagerId + ":" + fragmentPosition;
}

노트! 이것은 FragmentPagerAdapter새 조각을 만들 때 사용 하는 것과 동일한 방법입니다 . http://code.google.com/p/openintents/source/browse/trunk/compatibility/AndroidSupportV2/src/android/support/v2/app/FragmentPagerAdapter.java#104 링크를 참조하십시오 .


답변

조각 태그를 수동으로 생성 instantiateItem하여 내부 makeFragmentName메서드 와의 호환성 을 재정의 하거나 의존 할 필요가 없습니다 .
instantiateItemA는 공공 방법 당신이 할 수있는 그래서 실제로 해야 에 전화를 onCreate활동의 방법은 호출에 둘러싸여 startUpdatefinishUpdate에 설명 된 방법 PagerAdapter 의 javadoc :

PagerAdapter 메서드 startUpdate (ViewGroup)에 대한 호출은 ViewPager의 내용이 곧 변경됨을 나타냅니다. instantiateItem (ViewGroup, int) 및 / 또는 destroyItem (ViewGroup, int, Object)에 대한 하나 이상의 호출이 이어지며 업데이트의 끝은 finishUpdate (ViewGroup) 호출에 의해 신호를받습니다.

그런 다음 위의 방법으로 필요한 경우 조각의 인스턴스에 대한 참조를 로컬 변수에 저장할 수 있습니다. 예보기 :

public class MyActivity extends AppCompatActivity {

    Fragment0 tab0; Fragment1 tab1;

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.myLayout);
        ViewPager viewPager = (ViewPager) findViewById(R.id.myViewPager);
        MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(adapter);
        ((TabLayout) findViewById(R.id.tabs)).setupWithViewPager(viewPager);

        adapter.startUpdate(viewPager);
        tab0 = (Fragment0) adapter.instantiateItem(viewPager, 0);
        tab1 = (Fragment1) adapter.instantiateItem(viewPager, 1);
        adapter.finishUpdate(viewPager);
    }

    class MyPagerAdapter extends FragmentPagerAdapter {

        public MyPagerAdapter(FragmentManager manager) {super(manager);}

        @Override public int getCount() {return 2;}

        @Override public Fragment getItem(int position) {
            if (position == 0) return new Fragment0();
            if (position == 1) return new Fragment1();
            return null;  // or throw some exception
        }

        @Override public CharSequence getPageTitle(int position) {
            if (position == 0) return getString(R.string.tab0);
            if (position == 1) return getString(R.string.tab1);
            return null;  // or throw some exception
        }
    }
}

instantiateItem먼저에서 기존 조각 인스턴스에 대한 참조를 가져 오려고합니다 FragmentManager. 아직 존재하지 않는 경우에만 getItem어댑터의 방법을 사용하여 새 항목을 만들고 FragmentManager나중에 사용할 수 있도록에 “저장”합니다 .

조각에 대한 참조를 얻을 필요가 없더라도 다음 과 같이 메서드 에서 / 로 instantiateItem둘러싸인 모든 탭을 호출해야한다는 점에 유의해야 합니다.startUpdatefinishUpdateonCreate

    adapter.startUpdate(viewPager);
    // ignoring return values of the below 2 calls, just side effects matter:
    adapter.instantiateItem(viewPager, 0);
    adapter.instantiateItem(viewPager, 1);
    adapter.finishUpdate(viewPager);

그렇게하지 않으면, 당신은 당신의 조각 인스턴스는 않을 것이라는 점을 걸고 최선을 다하고 없습니다FragmentManager활동 전경이 될 때 : instantiateItem당신의 조각을 얻기 위해 자동으로 호출되지만, startUpdate/ finishUpdate 없습니다 (구현 세부 사항에 따라) 기본적으로 그들이 무엇을 해야 할 일은 시작 / 커밋입니다 FragmentTransaction.
및 필요한 것보다 훨씬 더 자주 재 (당신이 당신의 화면을 회전 할 때, 예를 들어) 매우 신속하게 손실되는 생성 된 단편 인스턴스에 대한 참조 초래한다. 프래그먼트가 얼마나 무거운 지에 따라 무시할 수없는 성능 결과가있을 수 있습니다.
또한 이러한 경우 로컬 변수에 저장된 조각의 인스턴스 부실해집니다 : FragmentManager어떤 이유로 든 안드로이드 플랫폼이 그것들을 얻으려고 하면 실패 할 것이고 따라서 새로운 것을 만들고 사용하게 될 것입니다.하지만 여러분의 vars는 여전히 오래된 것을 참조 할 것입니다.


답변

내가 한 방식은 다음과 같이 WeakReferences의 Hashtable을 정의하는 것입니다.

protected Hashtable<Integer, WeakReference<Fragment>> fragmentReferences;

그런 다음 다음과 같이 getItem () 메서드를 작성했습니다.

@Override
public Fragment getItem(int position) {

    Fragment fragment;
    switch(position) {
    case 0:
        fragment = new MyFirstFragmentClass();
        break;

    default:
        fragment = new MyOtherFragmentClass();
        break;
    }

    fragmentReferences.put(position, new WeakReference<Fragment>(fragment));

    return fragment;
}

그런 다음 메서드를 작성할 수 있습니다.

public Fragment getFragment(int fragmentId) {
    WeakReference<Fragment> ref = fragmentReferences.get(fragmentId);
    return ref == null ? null : ref.get();
}

이것은 잘 작동하는 것 같고 나는 그것이

"android:switcher:" + viewId + ":" + position

FragmentPagerAdapter가 구현되는 방식에 의존하지 않기 때문입니다. 물론 FragmentPagerAdapter에 의해 조각이 해제되었거나 아직 생성되지 않은 경우 getFragment는 null을 반환합니다.

누구든지이 접근 방식에서 잘못된 점을 발견하면 의견을 환영합니다.


답변

나는 현재 조각에 대한 참조를 얻기 위해 작동하는이 방법을 만들었습니다.

public static Fragment getCurrentFragment(ViewPager pager, FragmentPagerAdapter adapter) {
    try {
        Method m = adapter.getClass().getSuperclass().getDeclaredMethod("makeFragmentName", int.class, long.class);
        Field f = adapter.getClass().getSuperclass().getDeclaredField("mFragmentManager");
        f.setAccessible(true);
        FragmentManager fm = (FragmentManager) f.get(adapter);
        m.setAccessible(true);
        String tag = null;
        tag = (String) m.invoke(null, pager.getId(), (long) pager.getCurrentItem());
        return fm.findFragmentByTag(tag);
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    }
    return null;
}


답변

@ personne3000가 제시 한 해결책은 좋은,하지만 한 가지 문제가 있습니다 활동이 배경으로 이동하고는, 복원 후 (일부 무료로 메모리를 얻기 위해) 시스템에 의해 살해됩니다 때 fragmentReferences비어있을 것입니다 때문에 getItem되지 않을 것 전화.

아래 클래스는 이러한 상황을 처리합니다.

public abstract class AbstractHolderFragmentPagerAdapter<F extends Fragment> extends FragmentPagerAdapter {

    public static final String FRAGMENT_SAVE_PREFIX = "holder";
    private final FragmentManager fragmentManager; // we need to store fragment manager ourselves, because parent's field is private and has no getters.

    public AbstractHolderFragmentPagerAdapter(FragmentManager fm) {
        super(fm);
        fragmentManager = fm;
    }

    private SparseArray<WeakReference<F>> holder = new SparseArray<WeakReference<F>>();

    protected void holdFragment(F fragment) {
        holdFragment(holder.size(), fragment);
    }

    protected void holdFragment(int position, F fragment) {
        if (fragment != null)
            holder.put(position, new WeakReference<F>(fragment));
    }

    public F getHoldedItem(int position) {
        WeakReference<F> ref = holder.get(position);
        return ref == null ? null : ref.get();
    }

    public int getHolderCount() {
        return holder.size();
    }

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) { // code inspired by Google's FragmentStatePagerAdapter implementation
        super.restoreState(state, loader);
        Bundle bundle = (Bundle) state;
        for (String key : bundle.keySet()) {
            if (key.startsWith(FRAGMENT_SAVE_PREFIX)) {
                int index = Integer.parseInt(key.substring(FRAGMENT_SAVE_PREFIX.length()));
                Fragment f = fragmentManager.getFragment(bundle, key);
                holdFragment(index, (F) f);
            }
        }
    }

    @Override
    public Parcelable saveState() {
        Bundle state = (Bundle) super.saveState();
        if (state == null)
            state = new Bundle();

        for (int i = 0; i < holder.size(); i++) {
            int id = holder.keyAt(i);
            final F f = getHoldedItem(i);
            String key = FRAGMENT_SAVE_PREFIX + id;
            fragmentManager.putFragment(state, key, f);
        }
        return state;
    }
}


답변

조각에 대한 핸들을 얻는 주요 도로 블록은 getItem ()에 의존 할 수 없다는 것입니다. 방향 변경 후 조각에 대한 참조는 null이되고 getItem ()이 다시 호출되지 않습니다.

다음은 태그를 가져 오기 위해 FragmentPagerAdapter의 구현에 의존하지 않는 접근 방식입니다. getItem ()에서 생성되었거나 조각 관리자에서 찾은 조각을 반환하는 instantiateItem ()을 재정의하십시오.

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Object value =  super.instantiateItem(container, position);

    if (position == 0) {
        someFragment = (SomeFragment) value;
    } else if (position == 1) {
        anotherFragment = (AnotherFragment) value;
    }

    return value;
}