조각이 재개된 후 onLoadFinished가 다시 호출되는 이유는 무엇입니까?
-
21-12-2019 - |
문제
로더에 특별한 문제가 있습니다.현재 이것이 내 코드의 버그인지 아니면 로더를 잘못 이해하고 있는지 확실하지 않습니다.
앱
문제는 대화에서 발생합니다(Whatsapp과 비슷한 것을 상상해 보세요).내가 사용하는 로더는 다음을 기반으로 구현됩니다. AsyncTaskLoader 예.지원 라이브러리를 사용하고 있습니다.
- OnCreate에서는 캐시된 메시지를 검색하기 위해 로더를 시작합니다.
- CachedMessageLoader가 완료되면 RefreshLoader를 시작하여 최신 메시지를 (온라인으로) 검색합니다.
- 고유한 ID로 각 로더 유형(예: 오프라인:1 온라인:2)
이것은 다음 예외를 제외하고 매우 잘 작동합니다.
문제
다른 조각을 열고(트랜잭션을 백스택에 추가한 다음) Back-Key를 사용하여 대화Fragment로 돌아가면, onLoadFinished
다음과 같이 다시 호출됩니다. 둘 다 이전의 결과입니다.이 호출은 프래그먼트가 로더를 다시 시작할 기회를 갖기 전에 발생합니다.
이전에 얻은 "오래된" 결과를 전달하면 메시지가 중복됩니다.
질문
- 왜 그 결과가 다시 전달되나요?
- 이 로더를 잘못 사용하고 있습니까?
- 결과가 한 번만 전달되도록 결과를 "무효화"할 수 있습니까? 아니면 중복된 결과를 직접 제거해야 합니까?
호출의 스택 추적
MyFragment.onLoadFinished(Loader, Result) line: 369
MyFragment.onLoadFinished(Loader, Object) line: 1
LoaderManagerImpl$LoaderInfo.callOnLoadFinished(Loader, Object) line: 427
LoaderManagerImpl$LoaderInfo.reportStart() line: 307
LoaderManagerImpl.doReportStart() line: 768
MyFragment(Fragment).performStart() line: 1511
FragmentManagerImpl.moveToState(Fragment, int, int, int, boolean) line: 957
FragmentManagerImpl.moveToState(int, int, int, boolean) line: 1104
BackStackRecord.popFromBackStack(boolean) line: 764
...
업데이트 1여기에 언급된 로더는 대화 조각에 의해 시작됩니다.
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
Bundle args = getArguments();
m_profileId = args.getString(ArgumentConstants.ARG_USERID);
m_adapter = new MessageAdapter(this);
if (savedInstanceState != null) {
restoreInstanceState(savedInstanceState);
}
if (m_adapter.isEmpty()) {
Bundle bundle = new Bundle();
bundle.putString(ArgumentConstants.ARG_USERID, m_profileId);
getLoaderManager().restartLoader(R.id.loader_message_initial, bundle, this);
} else {
// Omitted: Some arguments passed in Bundle
Bundle b = new Bundle().
getLoaderManager().restartLoader(R.id.loader_message_refresh, b, this);
}
}
@Override
public void onResume() {
super.onResume();
// Omitted: setting up UI state / initiating other loaders that work fine
}
@Override
public AbstractMessageLoader onCreateLoader(final int type, final Bundle bundle) {
final SherlockFragmentActivity context = getSherlockActivity();
context.setProgressBarIndeterminateVisibility(true);
switch (type) {
case R.id.loader_message_empty:
return new EmptyOnlineLoader(context, bundle);
case R.id.loader_message_initial:
return new InitialDBMessageLoader(context, bundle);
case R.id.loader_message_moreoldDB:
return new OlderMessageDBLoader(context, bundle);
case R.id.loader_message_moreoldOnline:
return new OlderMessageOnlineLoader(context, bundle);
case R.id.loader_message_send:
sendPreActions();
return new SendMessageLoader(context, bundle);
case R.id.loader_message_refresh:
return new RefreshMessageLoader(context, bundle);
default:
throw new UnsupportedOperationException("Unknown loader");
}
}
@Override
public void onLoadFinished(Loader<Holder<MessageResult>> loader, Holder<MessageResult> holder) {
if (getSherlockActivity() != null) {
getSherlockActivity().setProgressBarIndeterminateVisibility(false);
}
// Omitted: Error handling of result (can contain exception)
List<PrivateMessage> unreadMessages = res.getUnreadMessages();
switch (type) {
case R.id.loader_message_moreoldDB: {
// Omitted error handling (no data)
if (unreadMessages.isEmpty()) {
m_hasNoMoreCached = true;
// Launch an online loader
Bundle b = new Bundle();
// Arguments omitted
getLoaderManager().restartLoader(R.id.loader_message_moreoldOnline, b, ConversationFragment.this);
}
// Omitted: Inserting results into adapter
}
case R.id.loader_message_empty: { // Online load when nothing in DB
// Omitted: error/result handling handling
break;
}
case R.id.loader_message_initial: { // Latest from DB, when opening
// Omitted: Error/result handling
// If we found nothing, request online
if (unreadMessages.isEmpty()) {
Bundle b = new Bundle();
// Omitted: arguments
getLoaderManager().restartLoader(R.id.loader_message_empty, b, this);
} else {
// Just get new stuff
Bundle b = new Bundle();
// Omitted: Arguments
getLoaderManager().restartLoader(R.id.loader_message_refresh, b, this);
}
break;
}
// Omitted: Loaders that do not start other loaders, but only add returned data to the adapter
default:
throw new IllegalArgumentException("Unknown loader type " + type);
}
// Omitted: Refreshing UI elements
}
@Override
public void onLoaderReset(Loader<Holder<MessageResult>> arg0) { }
업데이트 2내 MainActivity(궁극적으로 모든 조각을 호스팅함)는 SherlockFragmentActivity를 하위 클래스로 지정하고 기본적으로 다음과 같은 조각을 시작합니다.
Fragment f = new ConversationFragment(); // Setup omitted
f.setRetainInstance(false);
// Omitted: Code related to navigation drawer
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.fragment_container_frame, f).commit();
대화 조각은 다음과 같이 "디스플레이 프로필" 조각을 시작합니다.
DisplayProfileFragment f = new DisplayProfileFragment();
// Arguments omitted
FragmentManager manager = getSherlockActivity().getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.fragment_container_frame, f).addToBackStack(null).commit();
해결책
다음과 같은 다른 유사한 질문이 있습니다. 기계적 인조 인간:LoaderCallbacks.OnLoadFinished가 두 번 호출되었습니다. 그러나 로더 관리자 후크의 동작은 그대로입니다.첫 번째 결과 세트를 얻은 후 로더를 삭제할 수 있습니다.
public abstract void destroyLoader (int id)
또는 onLoaderReset을 처리하고 UI 데이터를 로더 데이터에 더 밀접하게 연결할 수 있습니다.
public abstract void onLoaderReset (Loader<D> loader)
이전에 생성 된 로더가 재설정 될 때 호출되어 데이터를 사용할 수 없습니다.이 시점에서 응용 프로그램은 로더 데이터에 대한 참조를 제거해야합니다.
개인적으로 저는 이를 위해 ContentProvider와 CursorLoader를 사용하겠습니다(각 데이터 행에는 고유한 _ID가 필요하지만 문제가 되지 않는 메시지의 경우).