android - Loader unable to retain itself during certain configuration change -
according http://developer.android.com/guide/components/loaders.html, 1 of nice thing loader that, able retain data during configuration change.
they automatically reconnect last loader's cursor when being recreated after configuration change. thus, don't need re-query data.
however, doesn't work in scenarios.
i take following simple example. fragmentactivity
, hosting fragment
. fragment
owns asynctaskloader
.
the following 3 scenarios work pretty well.
during first launched (ok)
1 loader created, , loadinbackground
executed once.
during simple rotation (ok)
no new loader being created , loadinbackground
not being triggered.
a child activity launched, , button pressed (ok)
no new loader being created , loadinbackground
not being triggered.
however, in following scenario.
a child activity launched -> rotation -> button pressed (wrong)
at time, old loader's onreset
called. old loader destroyed. new loader created , new loader's loadinbackground
triggered again.
the correct behavior i'm expecting is, no new loader created.
the loader related code follow. run code under android 4.1 emulator.
package com.example.bug; import android.content.context; import android.os.bundle; import android.support.v4.app.fragment; import android.support.v4.app.loadermanager; import android.support.v4.content.asynctaskloader; import android.support.v4.content.loader; import android.util.log; import android.view.layoutinflater; import android.view.view; import android.view.viewgroup; public class mainfragment extends fragment implements loadermanager.loadercallbacks<integer> { private static class integerarrayloader extends asynctaskloader<integer> { private integer result = null; public integerarrayloader(context context) { super(context); log.i("cheok", "integerarrayloader created!"); } @override public integer loadinbackground() { log.i("cheok", "time consuming loadinbackground!"); this.result = 123456; return result; } /** * handles request cancel load. */ @override public void oncanceled(integer integer) { super.oncanceled(integer); } /** * handles request stop loader. * automatically called loadermanager via stoploading. */ @override protected void onstoploading() { // attempt cancel current load task if possible. cancelload(); } /** * handles request start loader. * automatically called loadermanager via startloading. */ @override protected void onstartloading() { if (this.result != null) { deliverresult(this.result); } if (takecontentchanged() || this.result == null) { forceload(); } } /** * handles request reset loader. * automatically called loadermanager via reset. */ @override protected void onreset() { super.onreset(); // ensure loader stopped onstoploading(); // @ point can release resources associated 'apps' // if needed. this.result = null; } } @override public loader<integer> oncreateloader(int arg0, bundle arg1) { log.i("cheok", "oncreateloader being called"); return new integerarrayloader(this.getactivity()); } @override public void onloadfinished(loader<integer> arg0, integer arg1) { result = arg1; } @override public void onloaderreset(loader<integer> arg0) { // todo auto-generated method stub } public view oncreateview(layoutinflater inflater, viewgroup container, bundle savedinstancestate) { view v = inflater.inflate(r.layout.fragment_main, container, false); return v; } // http://stackoverflow.com/questions/11293441/android-loadercallbacks-onloadfinished-called-twice @override public void onresume() { super.onresume(); if (result == null) { // prepare loader. either re-connect existing one, // or start new one. getloadermanager().initloader(0, null, this); } else { // restore previous state. perhaps through long pressed home // button. } } private integer result; }
complete source code can downloaded https://www.dropbox.com/s/n2jee3v7cpwvedv/loader_bug.zip
this might related 1 unsolved android bug : https://code.google.com/p/android/issues/detail?id=20791&can=5&colspec=id%20type%20status%20owner%20summary%20stars
https://groups.google.com/forum/?fromgroups=#!topic/android-developers/dbkl6pvyhli
i wondering, there workaround on bug?
my answer quite straight forward actually. don't use asynctaskloaders. because few bugs regarding asynctaskloaders knew now.
a combination retainable (setretaininstance(true) in onactivitycreated()) fragment asynctask. works same way. have restructure code bit.
message op
although author doesn't provide code example, closest workable solution. not use author proposed solution. instead, still rely on asynctaskloader
necessary loading task. workaround that, rely on additional retained fragment, determine whether should reconnect/create loader. skeleton code on whole idea. works pretty far long can tell.
@override public void onactivitycreated(bundle savedinstancestate) { ... dataretainedfragment = (dataretainedfragment)fm.findfragmentbytag(date_retained_fragment); // dataretainedfragment can null still... } @override public void onresume() { ... if (this.data == null) { if (dataretainedfragment != null) { // re-use! onloadfinished(null, dataretainedfragment); } else { // prepare loader. either re-connect existing one, // or start new one. getloadermanager().initloader(0, null, this); } } else { } } @override public void onloadfinished(loader<data> arg0, data data) { this.data = data; if (this.dataretainedfragment == null) { this.dataretainedfragment = dataretainedfragment.newinstance(this.data); fragmentmanager fm = getfragmentmanager(); fm.begintransaction().add(this.dataretainedfragment, date_retained_fragment).commitallowingstateloss(); }
Comments
Post a Comment