LoaderManager에서 initLoader와 restartLoader의 차이점


129

initLoaderrestartLoader기능 과의 차이점에 대해 완전히 잃어 버렸습니다 LoaderManager.

  • 둘 다 같은 서명을 가지고 있습니다.
  • restartLoader 로더가없는 경우에도 로더를 작성합니다 ( "이 관리자에서 새 로더를 시작하거나 기존 로더를 다시 시작").

두 방법 사이에 어떤 관계가 있습니까? 전화는 restartLoader항상 전화 initLoader합니까? 전화 restartLoader하지 않고 전화 를 걸 수 initLoader있습니까? initLoader데이터를 새로 고치기 위해 두 번 전화하는 것이 안전 합니까? 때 나는 두 가지 중 하나를 사용한다 ?

답변:


202

이 질문에 대답하려면 LoaderManager코드 를 파헤쳐 야 합니다. LoaderManager 자체에 대한 문서가 명확하지 않거나 (이 질문이 없을 수도 있음) 추상 LoaderManager의 하위 클래스 인 LoaderManagerImpl에 대한 문서가 훨씬 더 밝아졌습니다.

initLoader

로더로 특정 ID를 초기화하려면 호출하십시오. 이 ID에 이미 연결된 로더가있는 경우 변경되지 않은 상태로 유지되며 이전 콜백은 새로 제공된 콜백으로 바뀝니다. 현재 ID에 대한 로더가없는 경우 새 로더가 작성되어 시작됩니다.

이 기능은 일반적으로 구성 요소를 초기화 할 때이를 사용하여 해당 구성 요소에 의존하는 로더가 작성되도록해야합니다. 이를 통해 기존 로더 데이터가 이미있는 경우이를 재사용 할 수 있습니다. 예를 들어 구성 변경 후 활동을 다시 작성할 때 로더를 다시 작성할 필요가 없습니다.

restartLoader

특정 ID와 연관된 로더를 다시 작성하기 위해 호출하십시오. 현재이 ID와 연관된 로더가 있으면 적절하게 취소 / 중지 / 파기됩니다. 주어진 인수를 가진 새로운 로더가 생성되고 사용 가능한 데이터가 전달됩니다.

[...]이 함수를 호출하면이 ID와 관련된 이전 로더는 유효하지 않은 것으로 간주되며 더 이상 데이터 업데이트를받지 않습니다.

기본적으로 두 가지 경우가 있습니다.

  1. ID가있는 로더가 존재하지 않습니다. 두 방법 모두 새 로더를 생성하므로 차이가 없습니다.
  2. ID가있는 로더가 이미 존재합니다 : initLoader매개 변수로 전달 된 콜백 만 교체하지만 로더를 취소하거나 중지하지는 않습니다. 의 경우 CursorLoader커서가 열려 있고 활성 상태를 유지함을 의미합니다 ( initLoader통화 이전의 경우 ). 반면에, restartLoader는 로더를 취소, 중지 및 파괴하고 (커서와 같은 기본 데이터 소스를 닫음) 새 로더를 생성합니다 (로더가 다음과 같은 경우 새 커서를 생성하고 쿼리를 다시 실행 함) CursorLoader).

다음은 두 방법 모두에 대한 간단한 코드입니다.

initLoader

LoaderInfo info = mLoaders.get(id);
if (info == null) {
    // Loader doesn't already exist -> create new one
    info = createAndInstallLoader(id, args, LoaderManager.LoaderCallbacks<Object>)callback);
} else {
   // Loader exists -> only replace callbacks   
   info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}

restartLoader

LoaderInfo info = mLoaders.get(id);
if (info != null) {
    LoaderInfo inactive = mInactiveLoaders.get(id);
    if (inactive != null) {
        // does a lot of stuff to deal with already inactive loaders
    } else {
        // Keep track of the previous instance of this loader so we can destroy
        // it when the new one completes.
        info.mLoader.abandon();
        mInactiveLoaders.put(id, info);
    }
}
info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);

로더가 존재하지 않는 경우 (info == null) 두 메소드 모두 새 로더를 작성합니다 (info = createAndInstallLoader (...)). 로더가 이미 존재 initLoader하는 restartLoader경우 기존 로더 를 비활성화하는 동안 콜백 (info.mCallbacks = ...) 만 교체하고 ( 새 로더가 작업을 완료하면 소멸됨) 새 로더를 작성합니다.

따라서 언제 사용 initLoader하고 언제 사용 restartLoader해야하는지, 그리고 두 가지 방법을 갖는 것이 합리적이라고 분명해졌습니다 . initLoader초기화 된 로더가 있는지 확인하는 데 사용됩니다. 존재하지 않는 경우 새로운 것이 생성되고 이미 존재하는 경우 재사용됩니다. 실행할 쿼리가 변경 되었기 때문에 (기본 데이터가 아니라 CursorLoader의 SQL 문과 같은 실제 쿼리) 새 로더가 필요한 경우가 아니면 항상이 메소드를 사용합니다.이 경우을 호출 restartLoader합니다.

활동 / 조각 라이프 사이클은 하나를 사용하기로 결정 또는 다른 방법을 함께 할 수 없다 (그리고 사이먼이 제안 원샷 플래그를 사용하여 전화를 추적 할 필요가 없습니다)! 이 결정은 전적으로 새로운 로더에 대한 "필요"에 기초하여 이루어집니다. 우리가 사용하는 동일한 쿼리를 실행하려면 initLoader다른 쿼리를 실행하려면 사용 restartLoader합니다.

우리는 항상 사용할 수 restartLoader있지만 비효율적입니다. 화면 회전 후 또는 사용자가 앱에서 다른 곳으로 이동 한 후 나중에 동일한 활동으로 돌아 가면 일반적으로 동일한 쿼리 결과를 표시하려고하므로 restartLoader불필요하게 로더를 다시 만들고 기본 (잠재적으로 비싼) 쿼리 결과를 무시합니다.

로드 된 데이터와 해당 데이터를로드하는 "쿼리"의 차이점을 이해하는 것이 매우 중요합니다. 주문을 위해 테이블을 쿼리하는 CursorLoader를 사용한다고 가정 해 봅시다. 새 주문이 해당 테이블에 추가되면 CursorLoader는 onContentChanged ()를 사용하여 UI에 새 주문을 업데이트하고 표시하도록 지시합니다 ( restartLoader이 경우에는 사용할 필요 없음 ). 미결 주문 만 표시하려면 새 쿼리가 필요하며 새 쿼리를 restartLoader반영하는 새 CursorLoader를 반환하는 데 사용 합니다.


두 방법 사이에 어떤 관계가 있습니까?

코드를 공유하여 새 로더를 작성하지만 로더가 이미 존재하면 다른 작업을 수행합니다.

전화는 restartLoader항상 전화 initLoader합니까?

아닙니다.

전화 restartLoader하지 않고 전화 를 걸 수 initLoader있습니까?

예.

initLoader데이터를 새로 고치기 위해 두 번 전화하는 것이 안전 합니까?

initLoader두 번 호출해도 안전 하지만 데이터가 새로 고쳐지지 않습니다.

때 나는 두 가지 중 하나를 사용한다 ?


위의 설명 후에 (희망적으로) 분명해야합니다.

구성 변경

LoaderManager는 구성 변경 (방향 변경 포함)에서 상태를 유지하므로 우리가 할 일이 없다고 생각할 것입니다. 다시 생각 해봐...

우선, LoaderManager는 콜백을 유지하지 않으므로 아무 것도 수행하지 않으면 콜백 메소드 등의 호출을 수신하지 않으므로 onLoadFinished()앱이 손상 될 가능성이 큽니다.

따라서 initLoader콜백 메소드를 복원하기 위해 최소한 호출해야합니다 ( restartLoader물론 가능합니다). 설명서 에는 다음 이 명시되어 있습니다.

호출 시점에 호출자가 시작 상태에 있고 요청 된 로더가 이미 존재하고 데이터를 생성 한 경우 콜백 onLoadFinished(Loader, D)이 즉시이 함수 내에서 호출 됩니다 ([...]).

initLoader, 방향 변경 후 onLoadFinished전화를 걸면 데이터가 이미로드되어 있기 때문에 즉시 전화를받을 수 있습니다 (변경 전의 경우라고 가정). 그 소리는 곧 들리지만 까다로울 수 있습니다 (우리 모두 Android를 좋아하지는 않습니다 ...).

우리는 두 가지 경우를 구별해야합니다.

  1. 구성 변경 자체를 처리합니다. setRetainInstance (true)를 사용하는 프래그먼트 또는 android:configChanges매니페스트에서 관련 태그가 있는 활동의 경우입니다 . 이러한 구성 요소는 화면 회전과 같은 onCreate 호출을받지 않으므로 initLoader/restartLoader다른 콜백 메서드 (예 : in onActivityCreated(Bundle)) 를 호출해야합니다 . 로더를 초기화하려면 로더 ID를 저장해야합니다 (예 : 목록). 구성 요소가 구성 변경 사항에 걸쳐 유지되므로 기존 로더 ID를 반복하고 호출 할 수 initLoader(loaderid, ...)있습니다.
  2. 구성 변경 자체를 처리하지 않습니다.이 경우 onCreate에서 로더를 초기화 할 수 있지만 로더 ID를 수동으로 유지해야하거나 필요한 initLoader / restartLoader 호출을 수행 할 수 없습니다. id가 ArrayList에 저장되어 있으면 initLoader 호출을 수행하기 전에
    outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray)onSaveInstanceState에서 수행하고 onCreate :에서 ID를 복원합니다 loaderIdsArray = savedInstanceState.getIntegerArrayList(loaderIdsKey).

: +1 : 마지막 포인트. initLoader회전 후 사용하고 (모든 콜백이 완료되고 로더가 유휴 상태 인 경우) onLoadFinished콜백을받지 않지만 사용 restartLoader하면 사용 합니까?
Blundell

잘못되었습니다. initLoader 메소드는 onloadFinished () 메소드를 리턴하기 전에 (로더가 시작되고 데이터가있는 경우) 호출합니다. 자세한 내용을 설명하기 위해 구성 변경에 대한 단락을 추가했습니다.
엠마누엘 Moecklin

6
아, 물론 당신의 대답과 @alexlockwood의 조합은 전체 그림을 제공합니다. 다른 사람들에 대한 대답은, 당신의 쿼리가 정적 인 경우 initLoader를 사용하고 쿼리를 변경하려는 경우 restartLoader를 사용하는 것 같습니다
Blundell

1
"조회가 정적 인 경우 initLoader를 사용하고 조회를 변경하려는 경우 restartLoader를 사용하십시오"
Emanuel Moecklin

1
@Mhd. Tahawi는 콜백을 변경하지 않고 원하는 곳으로 만 콜백을 설정합니다. 화면 회전 후 Android에서 메모리 누수를 방지하기 위해 화면을 유지하지 않기 때문에 화면을 다시 설정해야합니다. 그들이 옳은 일을하는 한 원하는대로 자유롭게 설정할 수 있습니다.
Emanuel Moecklin

46

initLoader로더가 이미 작성되었을 때 (이는 일반적으로 구성 변경 후 발생) 호출 하면 로더의 최신 데이터를 onLoadFinished즉시 전달하도록 LoaderManager에 지시합니다 . 로더가 아직 작성되지 않은 경우 (예 : 활동 / 조각이 처음 initLoader실행될 때) 호출하면 LoaderManager에 onCreateLoader새 로더 작성 을 호출 하도록 지시합니다 .

호출 restartLoader하면 이미 존재하는 로더 및 이와 연관된 기존 데이터가 모두 파괴되고 LoaderManager가 onCreateLoader새 로더를 작성하고 새로드를 시작 하도록 호출 하도록 지시합니다 .


설명서도 이것에 대해 매우 분명합니다.

  • initLoader로더가 초기화되고 활성화되도록합니다. 로더가 아직 없으면 로더가 작성되고 (활동 / 조각이 현재 시작된 경우) 로더가 시작됩니다. 그렇지 않으면 마지막으로 생성 된 로더가 재사용됩니다.

  • restartLoader이 관리자에서 새 로더를 시작하거나 기존 로더를 다시 시작하고 콜백을 등록하고 (활동 / 조각이 현재 시작된 경우)로드를 시작합니다. 동일한 ID를 가진 로더가 이전에 시작된 경우 새 로더가 작업을 완료하면 자동으로 삭제됩니다. 이전 로더가 파괴되기 전에 콜백이 전달됩니다.


@ TomanMoney 내 대답에 의미가 무엇인지 설명했습니다. 어떤 부분이 혼란 스럽습니까?
Alex Lockwood

방금 문서를 다시 해시했습니다. 그러나 의사는 각 방법을 사용해야하는 위치와 엉망이되는 이유에 대해서는 표시하지 않습니다. 내 경험에 따르면, 단지 restartLoader를 호출하고 initLoader를 호출하지 마십시오. 그래서 이것은 여전히 ​​혼란 스럽습니다.
Tom anMoney

3
@TomanMoney 일반적으로 활동 / 조각이 처음 시작될 때 / initLoader()에서 사용 합니다. 이 방법은 사용자가 처음 활동을 열 때, 로더가 처음으로 작성됩니다 ...하지만 전체 활동 / 조각 파괴해야 이후의 구성 변경에에 다음 호출은 단순히 이전을 반환 하는 대신 새로운 것을 만듭니다. 일반적으로 의 쿼리 를 변경해야 할 때 사용 합니다 (예 : 필터링 / 정렬 데이터 등을 원함). onCreate()onActivityCreated()initLoader()LoaderrestartLoader()Loader
Alex Lockwood

4
두 서명이 모두 동일한 서명을 가지고 있기 때문에 API 결정에 대해 여전히 혼란 스럽습니다. API가 매번 "올바른 것"을 수행하는 단일 startLoader () 메소드가 될 수없는 이유는 무엇입니까? 이것이 많은 사람들을 혼란스럽게하는 부분이라고 생각합니다.
Tom anMoney

1
@TomanMoney 여기 문서는 developer.android.com/guide/components/loaders.html 입니다. "구성 변경 후 다시 생성 될 때 마지막 로더 커서에 자동으로 다시 연결됩니다. 따라서 데이터를 다시 쿼리 할 필요가 없습니다."
IgorGanapolsky

16

최근에 여러 로더 관리자와 화면 방향 변경으로 인해 문제가 발생했으며 많은 시행 착오 후에 다음과 같은 패턴이 활동과 조각 모두에서 나에게 적합하다고 말하고 싶습니다.

onCreate: call initLoader(s)
          set a one-shot flag
onResume: call restartLoader (or later, as applicable) if the one-shot is not set.
          unset the one-shot in either case.

(그래서 즉, 일부 플래그를 설정 initLoader이 되어 항상 한 번 & restartLoader가 실행되는 실행 2 차 및 이후의 를 통과 onResume )

또한 액티비티 내의 각 로더에 서로 다른 ID를 할당해야합니다 (번호 매김에주의하지 않으면 해당 액티비티 내의 조각에 약간의 문제가 될 수 있음)


initLoader 만 사용하려고 시도했지만 .... 효과적으로 작동하지 않는 것 같습니다.

시도 initLoader을에서 onCreate 널 인수와 & (문서는이 괜찮 말한다) restartLoader 에서 (유효한 인수 포함) onResume 문서가 잘못 & ... initLoader가 nullpointer 예외가 발생합니다.

Tried restartLoader 만 해당 ... 잠시 동안 작동하지만 5 번 또는 6 번 화면 방향이 바뀝니다 .

onResume의 initLoader 를 시도 했습니다 . 다시 잠시 동안 작동하고 불면. (특히 "시작되지 않은 경우 호출 된 doRetain :"... 오류)

다음을 시도했습니다 : (로더 ID가 생성자에 전달 된 커버 클래스에서 발췌)

/**
 * start or restart the loader (why bother with 2 separate functions ?) (now I know why)
 * 
 * @param manager
 * @param args
 * @deprecated use {@link #restart(LoaderManager, Bundle)} in onResume (as appropriate) and {@link #initialise(LoaderManager, Bundle)} in onCreate 
 */
@Deprecated 
public void start(LoaderManager manager, Bundle args) {
    if (manager.getLoader(this.id) == null) {
        manager.initLoader(this.id, args, this);
    } else {
        manager.restartLoader(this.id, args, this);
    }
}

(스택 오버플로 어딘가에서 찾았습니다)

다시 말하지만, 이것은 잠시 동안 작동했지만 가끔씩 결함이 발생했습니다.


디버깅하는 동안 알아낼 수있는 것에서 initLoader (/ s)가 사이클의 스핀 을 견뎌야 하는 경우 수명주기 의 onCreate 부분 에서 initLoader (/ s)를 실행 해야하는 인스턴스 저장 / 복원 상태와 관련이 있다고 생각합니다. . (잘못되었을 수도 있습니다.)

다른 관리자 또는 작업에서 결과가 다시 나타날 때까지 (예 : onCreate 에서 초기화 할 수 없음) Manager를 시작할 수없는 경우 initLoader 만 사용 합니다 . (이것은 정확하지 않을 수도 있지만 작동하는 것 같습니다. 이러한 보조 로더는 인스턴스 인스턴스 상태의 일부가 아니므로 initLoader를 사용 하면 실제로이 경우 정확 할 수 있습니다)

라이프 사이클


다이어그램과 문서를 보면 initLoader가 onRestart for Activities의 onCreate & restartLoader로 이동해야하지만 조각이 다른 패턴을 사용하여 조각이 남아 실제로 이것이 안정적인지 조사 할 시간이 없다고 생각했을 것입니다. 이 활동 패턴에 성공한 사람은 누구든지 의견을 말할 수 있습니까?


/ @ Simon은 100 % 정확하며 이것이 정답입니다. 나는 그의 대답을 믿지 않았고 몇 시간 동안이 일을하는 다른 방법을 찾으려고 노력했습니다. initLoader 호출을 onCreate로 이동하자마자 작업이 시작되었습니다. 그러면 onStart가 호출되었지만 onCreate가없는 시간을 설명하기 위해 원샷 플래그가 필요합니다.
CjS

2
"Tried restartLoader 만 해당 ... 잠시 동안 작동하지만 5 번 또는 6 번 화면 방향이 바뀝니다." 그렇습니다? 방금 시도해 보았고 화면을 백 번 돌리고 폭발하지 않았습니다. 어떤 예외가 있습니까?
Tom anMoney

-1이 답변의 연구 노력에 감사하지만 대부분의 결과는 정확하지 않습니다.
Emanuel Moecklin

1
@ IgorGanapolsky 거의 모든 것. 내 대답을 읽고 이해하면 initLoader 및 restartLoader의 기능과 사용시기를 이해하고 Simon의 거의 모든 결론이 왜 틀린지도 이해할 수 있습니다. 프래그먼트 / 액티비티의 수명주기와 initLoader / restartLoader를 사용할 때의 결정 사이에는 연결이 없습니다 (구성 변경에서 설명하는 경고와 함께). Simon은 시행 착오에서 라이프 사이클이 두 가지 방법을 이해하는 단서이지만 결론은 아닙니다.
엠마누엘 Moecklin

@IgorGanapolsky 나는 내 자신의 답변을 광고하려고하지 않습니다. 다른 개발자를 돕고 Simon의 결과를 자신의 앱에 사용하지 못하게하려고합니다. 두 가지 방법의 의미를 이해하면 모든 것이 상당히 명확하고 구현하기가 쉬워집니다.
엠마누엘 Moecklin

0

initLoader로더가 이미 존재하는 경우 동일한 매개 변수를 재사용합니다. 새 매개 변수로 호출하더라도 이전 데이터가 이미로드 된 경우 즉시 리턴합니다. 로더는 이상적으로 새 데이터의 활동을 자동으로 알려야합니다. 화면이 회전하면 initLoader다시 호출되고 이전 데이터가 즉시 표시됩니다.

restartLoader다시로드를 강제하고 매개 변수를 변경하려는 경우를위한 것입니다. 로더를 사용하여 로그인 화면을 만들 restartLoader려면 버튼을 클릭 할 때마다 호출됩니다 . 자격 증명이 잘못되어 버튼이 여러 번 클릭되었을 수 있습니다. initLoader로그인이 진행되는 동안 화면이 회전 된 경우 활동의 저장된 인스턴스 상태를 복원 할 때만 호출 합니다.


-1

로더가 이미 존재하면, restartLoader는 이전 것을 중지 / 취소 / 파기하는 반면, initLoader는 주어진 콜백으로이를 초기화합니다. 이 경우 이전 콜백이 수행하는 작업을 찾을 수는 없지만 포기할 것 같습니다.

http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/app/LoaderManager.java를 통해 스캔 했지만 정확한 내용을 찾을 수 없습니다. 차이점은 방법이 다른 일을한다는 것 외에도 다릅니다. 따라서 initLoader를 처음 사용하고 다음 시간 동안 다시 시작한다고 말하지만 각각의 정확한 동작을 확실하게 말할 수는 없습니다.


그리고 무엇을 할 initLoader이 경우에합니까?
테 오메가

-1

처음 시작할 때 Init 로더는 loadInBackground () 메소드를 사용하고 두 번째 시작에서는 생략됩니다. 내 의견으로는 더 나은 해결책은 다음과 같습니다.

Loader<?> loa; 
try {
    loa = getLoaderManager().getLoader(0);
} catch (Exception e) {
    loa = null;
}
if (loa == null) {
    getLoaderManager().initLoader(0, null, this);
} else {
    loa.forceLoad();
}

////////////////////////////////////////////////////////// ///////////////////////////

protected SimpleCursorAdapter mAdapter;

private abstract class SimpleCursorAdapterLoader 
    extends AsyncTaskLoader <Cursor> {

    public SimpleCursorAdapterLoader(Context context) {
        super(context);
    }

    @Override
    protected void onStartLoading() {
        if (takeContentChanged() || mAdapter.isEmpty()) {
            forceLoad();
        }
    }

    @Override
    protected void onStopLoading() {
        cancelLoad();
    }

    @Override
    protected void onReset() {
        super.onReset();
        onStopLoading();
    }
}

이 솔루션을 찾는 데 많은 시간을 보냈습니다. 필자의 경우 restartLoader (...)가 제대로 작동하지 않았습니다. 유일한 forceLoad ()를 사용하면 콜백없이 이전로드 스레드를 완료 할 수 있으므로 모든 DB 트랜잭션이 올바르게 완료됩니다. 새 스레드를 다시 시작합니다. 예, 시간이 좀 더 필요하지만 더 안정적입니다. 마지막으로 시작한 스레드 만 콜백을받습니다. 따라서 DB 트랜잭션을 중단하여 테스트하려는 경우 환영합니다. restartLoader (...)를, 그렇지 않으면 forceLoad ()를 다시 시도하십시오. restartLoader (...)의 유일한 편리함은 매개 변수를 의미하는 새로운 초기 데이터를 제공하는 것입니다. 그리고이 경우 적합한 Fragment의 onDetach () 메소드에서 로더를 파괴하는 것을 잊지 마십시오. 또한 때때로 활동이있을 때 그들에게 말하기를 명심하십시오. Loader가 포함 된 2 개의 프래그먼트 각각 하나의 포함 활동에 도달하게됩니다. 2 개의 로더 관리자에만 도달하므로 Activity는 LoaderManager와 Fragment (s)를 공유합니다. LoaderManager를 사용해보십시오 .enableDebugLogging (true); 각각의 특정 경우에 세부 사항을 볼 수 있습니다.


2
-1 호 배치에 대한 getLoader(0)A의 try { ... } catch (Exception e) { ... }.
Alex Lockwood 2012
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.