Потерянная ссылка на переменную после изменения конфигурации - PullRequest
0 голосов
/ 10 марта 2012

У меня есть acitivty, который ищет местоположение пользователя (класс MyLocation), затем с геопунктом или без него запускается AsyncTask для соединения с сервером и получения списка городов с моего сервера. Когда список готов, он сохраняет их в городах ArrayList. Как только ArrayList городов заполнен, я бы хотел, чтобы он был сохранен навсегда (доказательство изменений конфигурации). CityItem реализует Parcelable. Я сохраняю их в onSaveInstanceState и извлекаю их при создании.

Теперь все работает нормально, если задание выполнено и список городов заполнен. Затем я поворачиваю свое устройство вперед и назад и Log.i («StartActivity», «Загружен список городов:» + towns.toString ()); вызывается.

Но если я поверну устройство до того, как будет найден геопункт (или задача выполнена - трудно сказать, потому что это происходит быстро), тогда

public void gotCities(ArrayList<CityItem> _cities){

    cities = _cities;
    Log.i("StartActivity", "gotCities("+cities.size()+"): "+cities.toString());
}

вызывается (и в журнале все отлично), но когда я поворачиваю его еще раз, ArrayList городов снова становится нулевым .

Похоже, что если конфигурация изменилась, а saveInstanceState.cities был равен нулю, города ArrayList каким-то образом снова создаются, и это не тот же ArrayList, что и в функции gotCities ().

Я почти уверен, что это легко, но я часами искал ответ и просто не могу этого сделать.

Код Деятельности:

public class StartActivity extends Activity {

public static final String PREFS_NAME = "PrefsFile";

MyLocation myLocationObject = null;
LatLngPoint point = null;
ArrayList<CityItem> cities = null;

FindCityTask task = null;
Activity startActivity;

@Override
public void onCreate(Bundle savedInstanceState) {

if(savedInstanceState!=null) if(savedInstanceState.containsKey("cities")) cities = savedInstanceState.getParcelableArrayList("cities"); if(cities!=null) Log.i("Cities retrieved", cities.toString());

    super.onCreate(savedInstanceState);

    startActivity = this;

        setContentView(R.layout.start);

        //check if the configuration (orientation) has been changed
        NonConfigurationObject nco = (NonConfigurationObject)getLastNonConfigurationInstance();
        if(nco!=null) if(nco.myLocationObject!=null) myLocationObject = nco.myLocationObject;
        if(nco!=null) if(nco.task!=null) task = nco.task;

        if(cities==null){

            Log.i("StartActivity", "Cities list is empty - retrieve them.");

            if(myLocationObject==null){
                getGeopoint();
            }
        } else {
            Log.i("StartActivity", "Cities list downloaded:"+cities.toString());

    }

}

private void getGeopoint(){

    if(isOnline()){ //there is internet connection


        if(myLocationObject==null){

            myLocationObject = new MyLocation();
            //calls function to check user location (returns false if no providers are enabled
            if(!myLocationObject.getLocation(this, locationResult)){ /*TODO handle */Log.i("StartActivity", "Location providers disabled");}

        }


    } else { //not online - show msg

        Log.i("StartActivity", "No internet connection");

    }

}


//waits for user geopoint. then starts FindCityTask 
LocationResult locationResult = new LocationResult(){
    @Override
    public void gotLocation(final Location location){
        if(location!=null){

            // location found
            Log.i("StartActivity", "Received location: "+location.toString());
            point = new LatLngPoint((float)location.getLatitude(), (float)location.getLongitude());

        } else {

            // location not found   
            Log.i("StartActivity", "No location received after 20 seconds");
            point = null;

        }

        //RUN TASK to connect to server to get cities list (even if there's no geopoint)
        task = new FindCityTask(startActivity);
        task.execute(point);

    }
};

public void gotCities(ArrayList<CityItem> _cities){

    cities = _cities;
    Log.i("StartActivity", "gotCities("+cities.size()+"): "+cities.toString());
}

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    super.onSaveInstanceState(savedInstanceState);
    Log.i("onSaveInstanceState", "onSaveInstanceState");
    if(cities!=null) savedInstanceState.putParcelableArrayList("cities", cities);
}

@Override
public NonConfigurationObject onRetainNonConfigurationInstance() {

    NonConfigurationObject nco = new NonConfigurationObject();

    if(myLocationObject!=null){
        nco.myLocationObject = myLocationObject;
    }
    if(task!=null){
        nco.task = task;
    }

    return nco;

}

static class NonConfigurationObject{

    MyLocation myLocationObject;
    FindCityTask task;

}

Метод gotCities () вызывается из AsyncTask onPostExecute:

@Override
protected void onPostExecute(Void result) {
    if(this.activity!=null){
        ((StartActivity) activity).gotCities(cities);   
    }
}

Ответы [ 2 ]

0 голосов
/ 11 марта 2012

Я наконец получил это.Это AsyncTask, которая указала на неправильную (ту, что была до изменения конфигурации) активность.Хитрость заключается в том, чтобы прикрепить к задаче ссылку на действие (и сделать это в нужный момент).

Поэтому первое, что нужно сделать, это поместить эти функции в AsyncTask:

void attach(Activity activity){
    this.activity = activity;
}

void detach(){
    this.activity = null;
}

Когда задача вызывается впервые, мы должны прикрепить к ней действие.Затем onRetainNonConfigurationInstance () отсоедините его.

@Override    
public NonConfigurationObject onRetainNonConfigurationInstance() {

    //normally it would return only the task, but i have to return another object
    //hence the NonConfigurationObject which holds reference to both the AsyncTask and MyLocation
    //(as seen in the original question)

    NonConfigurationObject nco = new NonConfigurationObject();

    if(task!=null){
        task.detach();
        nco.task = task;
    }

    return nco;

}

И, наконец, присоедините его снова, когда мы вызываем getLastNonConfigurationInstance () в onCreate ().

        //check if the configuration (orientation) has been changed
        NonConfigurationObject nco = (NonConfigurationObject)getLastNonConfigurationInstance();

        if(nco!=null){ //not created for the first time

            Log.i("StartActivity", "NCO: "+nco.toString());
            task = nco.task;
            if(task!=null){ //nco can be present but task still null
                task.attach(this);
            } else {
                task = new FindCityTask(this);
            }

        } else {
            Log.i("StartActivity", "NCO: null");
            task = new FindCityTask(this);
        }

Пожалуйста, поделитесь своими мыслями об этом решении, если выесть какой-либо.Я изменю вопрос так, чтобы он лучше подходил к проблеме.

0 голосов
/ 10 марта 2012

Когда ориентационное нажатие попадает, ваша деятельность останавливается, уничтожается и создается заново, и единственный обратный вызов, который гарантированно вызывается, это onPause (). То же самое происходит, когда включается блокировка экрана - это вызывает вашу активность в портретном режиме.

Рекомендую прочитать о жизненном цикле активности Android. Правило большого пальца:

  • onCreate () для инициализации объектов интерфейса и служб
  • onResume () вызывается, когда ваша активность выходит на первое место и собирается быть представлена ​​пользователю
  • onPause (), когда он теряет фокус и больше не предназначен для использования.

Поскольку получение местоположения занимает много времени, лучше переместить его из действия в фоновую службу (при необходимости запустить его в onCreate ()) и отделить его жизненный цикл от действия. Сервис может передавать результаты деятельности через широковещательные сообщения или вызовы метода java

И загляните в свой onSaveInstanceState () - сначала вы вызываете super.onSaveInstanceState (), а затем вы модифицируете bundle для включения ваших данных. Таким образом, они никогда не сохраняются.

...