`LoaderManager`에서`initLoader`와`restartLoader`의 차이점
initLoader
의 restartLoader
기능 과의 차이점에 대해 완전히 잃어 버렸습니다 LoaderManager
.
- 둘 다 같은 서명을 가지고 있습니다.
restartLoader
로더가 없으면 로더를 작성합니다 ( "이 관리자에서 새 로더를 시작하거나 기존 로더를 다시 시작합니다").
두 방법 사이에 어떤 관계가 있습니까? 전화는 restartLoader
항상 전화 initLoader
합니까? 전화 restartLoader
하지 않고 전화 를 걸 수 initLoader
있습니까? initLoader
데이터를 새로 고치기 위해 두 번의 호출로 저장 됩니까? 두 가지 중 하나를 사용해야하는 이유는 무엇입니까?
이 질문에 대답하려면 LoaderManager 코드를 파헤쳐 야합니다. LoaderManager 자체에 대한 문서가 명확하지 않거나 (이 질문이 없을 수도 있음) 추상 LoaderManager의 하위 클래스 인 LoaderManagerImpl에 대한 문서가 훨씬 더 밝아졌습니다.
initLoader
로더로 특정 ID를 초기화하려면 호출하십시오. 이 ID에 이미 연결된 로더가 있으면 변경되지 않은 상태로 유지되며 이전 콜백은 새로 제공된 콜백으로 바뀝니다. 현재 ID에 대한 로더가없는 경우 새 로더가 작성되어 시작됩니다.
이 기능은 일반적으로 구성 요소를 초기화 할 때이를 사용하여 해당 구성 요소에 의존하는 로더가 작성되도록해야합니다. 이를 통해 기존 로더 데이터가 이미있는 경우이를 재사용 할 수 있습니다. 예를 들어 구성 변경 후 활동을 다시 작성할 때 로더를 다시 작성할 필요가 없습니다.
restartLoader
특정 ID와 연관된 로더를 다시 작성하기 위해 호출하십시오. 현재이 ID와 연관된 로더가 있으면 적절하게 취소 / 중지 / 파기됩니다. 주어진 인수를 가진 새로운 로더가 생성되고 사용 가능한 데이터가 전달됩니다.
[...]이 함수를 호출하면이 ID와 관련된 이전 로더는 유효하지 않은 것으로 간주되며 더 이상 데이터 업데이트를받지 않습니다.
기본적으로 두 가지 경우가 있습니다.
- ID가있는 로더가 존재하지 않습니다. 두 방법 모두 새 로더를 생성하므로 차이가 없습니다.
- 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는 콜백 (info.mCallbacks = ...) 만 교체하고 restartLoader는 이전 로더를 비활성화하고 (새 로더가 작업을 완료하면 소멸됨) 새 로더를 작성합니다.
Thus said it's now clear when to use initLoader and when to use restartLoader and why it makes sense to have the two methods. initLoader is used to ensure there's an initialized loader. If none exists a new one is created, if one already exists it's re-used. We always use this method UNLESS we need a new loader because the query to run has changed (not the underlying data but the actual query like in SQL statement for a CursorLoader), in which case we will call restartLoader.
Activity / Fragment 수명주기는 하나 또는 다른 방법을 사용하기로 결정한 것과 관련이 없습니다 (Simon이 제안한대로 원샷 플래그를 사용하여 통화를 추적 할 필요가 없습니다)! 이 결정은 전적으로 새로운 로더에 대한 "필요"에 기초하여 이루어집니다. 동일한 쿼리를 실행하려면 initLoader를 사용하고 다른 쿼리를 실행하려면 restartLoader를 사용합니다. 우리는 항상 restartLoader를 사용할 수 있지만 비효율적입니다. 화면 회전 후 또는 사용자가 앱에서 다른 곳으로 이동하여 나중에 동일한 활동으로 돌아 가면 일반적으로 동일한 쿼리 결과를 표시하기 위해 restartLoader가 불필요하게 로더를 다시 만들고 기본 (잠재적으로 비싼) 쿼리 결과를 닫습니다. .
로드 된 데이터와 해당 데이터를로드하는 "쿼리"의 차이점을 이해하는 것이 매우 중요합니다. 주문을 위해 테이블을 쿼리하는 CursorLoader를 사용한다고 가정 해 봅시다. 새 주문이 해당 테이블에 추가되면 CursorLoader는 onContentChanged ()를 사용하여 UI에 새 주문을 업데이트하고 표시하도록 지시합니다 (이 경우 restartLoader를 사용할 필요가 없음). 미결 주문 만 표시하려면 새 쿼리가 필요하고 restartLoader를 사용하여 새 쿼리를 반영하는 새 CursorLoader를 반환합니다.
두 방법 사이에 어떤 관계가 있습니까?
코드를 공유하여 새 로더를 작성하지만 로더가 이미 존재하면 다른 작업을 수행합니다.
restartLoader를 호출하면 항상 initLoader를 호출합니까?
아닙니다.
initLoader를 호출하지 않고 restartLoader를 호출 할 수 있습니까?
예.
데이터를 새로 고치기 위해 initLoader를 두 번 호출하는 것이 안전합니까?
initLoader를 두 번 호출해도 안전하지만 데이터가 새로 고쳐지지 않습니다.
두 가지 중 하나를 사용해야하는 이유는 무엇입니까?
위의 설명 후에 (희망적으로) 분명해야합니다.
구성 변경
LoaderManager는 구성 변경 (방향 변경 포함)에서 상태를 유지하므로 할 일이 없다고 생각할 것입니다. 다시 생각 해봐...
우선 LoaderManager는 콜백을 유지하지 않으므로 아무것도하지 않으면 onLoadFinished () 등과 같은 콜백 메소드에 대한 호출을 수신하지 않으므로 앱이 중단 될 가능성이 큽니다. 따라서 콜백 메소드를 복원하기 위해 적어도 initLoader를 호출해야합니다 (starterLoader도 물론 가능합니다). 이 문서 에는
호출 시점에 호출자가 시작 상태에 있고 요청 된 로더가 이미 존재하고 데이터를 생성 한 경우 콜백 onLoadFinished (Loader, D)가 즉시 호출됩니다 (이 함수 내에서) [...].
즉, 방향 변경 후 initLoader를 호출하면 데이터가 이미로드되어 있기 때문에 onLoadFinished 호출을 즉시 받게됩니다 (변경 전의 경우라고 가정). 그 소리는 곧 들리지만 까다로울 수 있습니다 (우리 모두 Android를 좋아하지는 않습니다 ...)
우리는 두 가지 경우를 구별해야합니다.
- 구성 변경 자체를 처리합니다. 이는 setRetainInstance (true)를 사용하는 프래그먼트 또는 매니페스트에 따라 android : configChanges 태그가있는 활동의 경우입니다. 이러한 컴포넌트는 화면 회전 후 onCreate 호출을 수신하지 않으므로 다른 콜백 메소드 (예 : onActivityCreated (Bundle))에서 initLoader / restartLoader를 호출해야합니다. 로더를 초기화하려면 로더 ID를 저장해야합니다 (예 : List). 구성 요소가 구성 변경 사항에 걸쳐 유지되므로 기존 로더 ID를 반복하고 initLoader (loaderid, ...)를 호출하면됩니다.
- Doesn't handle configuration changes itself: In this case the Loaders can be initialized in onCreate but we need to manually retain the loader ids or we won't be able to make the needed initLoader/restartLoader calls. If the ids are stored in an ArrayList, we would do an
outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray) in onSaveInstanceState and restore the ids in onCreate: loaderIdsArray = savedInstanceState.getIntegerArrayList(loaderIdsKey) before we make the initLoader call(s).
Calling initLoader
when the Loader has already been created (this typically happens after configuration changes, for example) tells the LoaderManager to deliver the Loader's most recent data to onLoadFinished
immediately. If the Loader has not already been created (when the activity/fragment first launches, for example) the call to initLoader
tells the LoaderManager to call onCreateLoader
to create the new Loader.
Calling restartLoader
destroys an already existing Loader (as well as any existing data associated with it) and tells the LoaderManager to call onCreateLoader
to create the new Loader and to initiate a new load.
The documentation is pretty clear about this too:
initLoader
ensures a Loader is initialized and active. If the loader doesn't already exist, one is created and (if the activity/fragment is currently started) starts the loader. Otherwise the last created loader is re-used.restartLoader
starts a new or restarts an existing Loader in this manager, registers the callbacks to it, and (if the activity/fragment is currently started) starts loading it. If a loader with the same id has previously been started it will automatically be destroyed when the new loader completes its work. The callback will be delivered before the old loader is destroyed.
I recently hit a problem with multiple loader managers and screen orientation changes and would like to say that after a lot of trial-and-error, the following pattern works for me in both Activities and Fragments:
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.
(in other words, set some flag so that initLoader is always run once & that restartLoader is run on the 2nd & subsequent passes through onResume)
Also, remember to assign different ids for each of your loaders within an Activity (which can be a bit of an issue with fragments within that activity if you're not careful with your numbering)
I tried using initLoader only .... didn't seem to work effectively.
Tried initLoader on onCreate with null args (docs say this is ok) & restartLoader (with valid args) in onResume ....docs are wrong & initLoader throws a nullpointer exception.
Tried restartLoader only ... works for a while but blows on 5th or 6th screen re-orientation.
Tried initLoader in onResume; again works for a while & then blows. (specifically the "Called doRetain when not started:"... error)
Tried the following : (excerpt from a cover class that has the loader id passed into the constructor)
/**
* 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);
}
}
(which I found somewhere in Stack-Overflow)
Again, this worked for a while but still threw the occasional glitch.
From what I can figure out while debugging, I think that there is something to do with save/restore instance state that requires that initLoader(/s) are run in the onCreate part of the lifecycle if they are to survive a spin of the cycle. ( I may be wrong.)
in the case of Managers that cannot be started until the results come back from another manager or task (ie. cannot be initialised in onCreate), I only use initLoader. (I may not be correct in this but it seems to work. These secondary loaders are not part of the immediate instance-state so using initLoader may actually be correct in this case)
Looking at the diagrams and docs, I would have thought that initLoader should go in onCreate & restartLoader in onRestart for Activities but that leaves Fragments using some different pattern and I've not had time to investigate if this is actually stable. Can anyone else comment on if they have success with this pattern for activities?
initLoader
will reuse the the same parameters if the loader already exists. It returns immediately if old data is already loaded, even if you call it with new parameters. The loader should ideally automatically notify the activity of new data. If the screen rotated, initLoader
would be called again and the old data would be immediately displayed.
restartLoader
is for when you want to force a reload and change the parameters as well. If you were to make a login screen using loaders, you would call only restartLoader
each time the button was clicked. (The button may be clicked multiple times due to incorrect credentials etc.). You would only ever call initLoader
when restoring the activity's saved instance state in the event the screen was rotated while a login was in progress.
If the loader already exists, restartLoader will stop/cancel/destroy the old one, while initLoader will just initialize it with the given callback. I cannot find out what the old callbacks do in these cases, but I guess they will just be abandoned.
I scanned through http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/app/LoaderManager.java but I cannot find out what the exact difference is, apart from that the methods do different things. So I would say, use initLoader the first time and restart for the following times, though I cannot say with certainty what each of them will do exactly.
Init loader on first start uses loadInBackground() method, on second start it will be omitted. So, my opinion, the better solution is:
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();
}
}
I spent many time to find this solution -- restartLoader(...) didn't work properly in my case. The only forceLoad() lets to finish previous loading thread without callback (so you will have all db transactions finished properly) and starts again new thread. Yes, it demands some extra time, but is more stable. Only the last started thread will take callback. Thus, if you want to make tests with interrupting your db transactions -- your welcome, try to restartLoader(...), otherwise forceLoad(). The only convenience of restartLoader(...) is to deliver new initial data, I mean parameters. And please don't forget to destroy loader in onDetach() method of the suitable Fragment in this case. Also keep in mind, that some times, when you have an activity and, let them say, 2 fragments with Loader each one inclusive activity -- you will reach only 2 Loader Managers, so Activity shares its LoaderManager with Fragment (s), that's shown on the screen first during loading. Try LoaderManager.enableDebugLogging(true); to see the details in each certain case.
'development' 카테고리의 다른 글
(유니 코드 오류) 'unicodeescape'코덱이 위치 2-3의 바이트를 디코딩 할 수 없습니다 : 잘린 \ UXXXXXXXX 이스케이프 (0) | 2020.07.05 |
---|---|
날짜 형식이 잘못된 MVC DateTime 바인딩 (0) | 2020.07.05 |
완료되면 몽구스의 연결을 올바르게 닫으십시오. (0) | 2020.07.05 |
주어진 속성에 대해 최대 값을 가진 객체를 LINQ가 반환하도록하려면 어떻게해야합니까? (0) | 2020.07.05 |
그래프 기반 데이터베이스 (http://neo4j.org/)의 사용 사례는 무엇입니까? (0) | 2020.07.05 |