Должен ли внутренний класс DialogFragment быть статическим или нет?

Это фрагмент кода из моего проекта, который я использую для изучения Android:

private void enableLocationSettings() {
    Intent settingsIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
    startActivity(settingsIntent);
}

@SuppressLint("ValidFragment")
public class EnableGpsDialogFragment extends DialogFragment {

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new AlertDialog.Builder(getActivity())
            .setTitle("Tytuł")
            .setMessage("wiadomosc")
            .setPositiveButton("odpal", new DialogInterface.OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                    enableLocationSettings();
                }

            })
            .create();
    }
} 

Как видите, я должен добавить @SuppressLint, чтобы приложение заработало, но в руководстве эта аннотация отсутствует. не нужно.

Что я делаю не так?

Вот мой импорт:

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.provider.Settings;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.DialogFragment;
import android.view.Menu;
import android.view.View;
import android.widget.TextView;
import android.widget.ToggleButton;

person szpic    schedule 14.03.2013    source источник


Ответы (3)


В примере нет этих аннотаций, потому что класс находится в собственном файле. Это означает, что он независим от действия, использующего фрагмент.

В вашем случае ваш фрагмент находится внутри действия и не использует статический модификатор. Это означает, что он привязан к экземпляру Activity.

Зависимость фрагмента от экземпляра Activity — плохая идея, оба этих класса имеют сложные жизненные циклы (особенно потому, что Activity довольно часто уничтожаются и воссоздаются) и должны быть независимы друг от друга.

Вам нужно будет сделать модификатор EnableGpsDialogFragment static.

public static class EnableGpsDialogFragment extends DialogFragment {

Класс static не зависит от экземпляра окружающего класса, поэтому предупреждение исчезнет.

Дополнительные сведения см. в руководстве по Java по вложенным классам.

Изменить в ответ на ваше редактирование: теперь, когда классы не зависят от экземпляра друг друга, вам нужно будет получить экземпляр YourActivity, чтобы вы могли вызывать enabledLocationSettings() одним из способов приведения и будет работать только в том случае, если EnableGpsDialogFragment используется только YourActivity:

@Override
public void onClick(DialogInterface dialog, int which) {
  enableLocationSettings();
}

to

@Override
public void onClick(DialogInterface dialog, int which) {
  ((YourActivity)getActivity()).enableLocationSettings();
}

Если этот фрагмент будет использоваться несколькими действиями, вам следует вместо этого создать interface для реализации каждым действием.

person A--C    schedule 14.03.2013
comment
Спасибо, но добавление статического модификатора в EnableGpsDialogFragment вынуждает меня добавлять статический модификатор в private void enableLocationSettings(). И если я добавлю его, появится новая ошибка: Невозможно сделать статическую ссылку на нестатический метод startActivity(Intent) из типа Activity - person szpic; 14.03.2013
comment
@szpic это потому, что ваше редактирование вашего вопроса теперь включает вызов enableLocationSettings(); - person A--C; 14.03.2013
comment
Чтобы обойти это, вы можете использовать слабую ссылку на все, что нужно от действия. Затем перед использованием убедитесь, что он все еще действителен. Это должно быть установлено каждый раз, когда активность уничтожается/создается, чтобы убедиться, что она действительна. Чтобы добавить о статических фрагментах: они могут привести к утечке памяти в этом контексте, если они не созданы статически, поскольку они могут содержать ссылку на старую активность, которая больше недоступна иным образом и должна быть собрана мусором. - person Zachary Moshansky; 14.03.2013
comment
Спасибо, ты спас мой день. Я хотел бы дать вам больше голосов! - person Ale; 21.11.2016

Все DialogFragments должны быть общедоступными и, если это внутренний класс, статическими. Они также должны иметь общедоступный конструктор без аргументов и полагаться только на setArguments() для передачи параметров.

Невыполнение этого требования в течение некоторого времени приводило к появлению предупреждения Lint, которое вы могли бы подавить, если бы действительно хотели, но, начиная с библиотеки поддержки Android v25 и выше, вы фактически получите исключение, если попытаетесь показать DialogFragment, который не t соблюдать эти правила:

java.lang.IllegalStateException: Fragment TestActivity$TestDialogFrament must be a public static class to be properly recreated from instance state.

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

К сожалению, это усложняет работу с диалогами, поскольку обычно очень удобно просто создать анонимный подкласс, который переопределяет onCreateDialog. Однако такой диалог вообще невозможно воссоздать.

person JHH    schedule 26.10.2016
comment
Ситуации с нехваткой памяти - реальная угроза, так как у нас в дешевых смартфонах как минимум 2 ГБ ОЗУ... :) Я никогда не понимал, почему Google хочет воссоздать фрагменты моих диалогов, если они должны были их уничтожить - я могу сделать это самостоятельно и уже сделайте так (я использую v23 и не буду обновляться). - person The incredible Jan; 07.07.2017
comment
Ну, отчасти потому, что так много приложений работают неправильно и в основном работают все время или постоянно перезапускаются из-за частых широковещательных намерений. Это серьезно решается в Android N и O. Если 50 приложений хотят работать все время, будет много подкачки приложений из памяти. Кроме того, помните, что на экране 1080p одно растровое изображение занимает 8 МБ. И я не совсем уверен, что понимаю, почему вы хотите полностью воссоздать состояние вручную, но у всех нас есть свои удовольствия... - person JHH; 06.09.2017

Так не должно быть !

С моей точки зрения, я не хочу, чтобы мой DialogFragment (ваш NetworkConnectionError) был статическим, потому что я хочу иметь возможность вызывать из него переменные или методы моего содержащего класса (Activity).
Так что это не будет статический. Но я также не хочу генерировать memoryLeaks.
Итак, каково решение?
Просто, когда вы входите в onStop, убедитесь, что вы убили свой DialogFragment, это так просто. Итак, код выглядит примерно так:

public class CarActivity extends AppCompatActivity{

/**
 * The DialogFragment networkConnectionErrorDialog 
 */
private NetworkConnectionError  networkConnectionErrorDialog ;
//...  your code ...//
@Override
protected void onStop() {
    super.onStop();
    //invalidate the DialogFragment to avoid stupid memory leak
    if (networkConnectionErrorDialog != null) {
        if (networkConnectionErrorDialog .isVisible()) {
            networkConnectionErrorDialog .dismiss();
        }
        networkConnectionErrorDialog = null;
    }
}
/**
 * The method called to display your dialogFragment
 */
private void onDeleteCurrentCity(){
    FragmentManager fm = getSupportFragmentManager();
     networkConnectionErrorDialog =(DeleteAlert)fm.findFragmentByTag("networkError");
    if(networkConnectionErrorDialog ==null){
        networkConnectionErrorDialog =new DeleteAlert();
    }
    networkConnectionErrorDialog .show(getSupportFragmentManager(), "networkError");
}

И таким образом вы избегаете утечек памяти (потому что это плохо) и гарантируете, что у вас нет гребаного статического фрагмента, который не может получить доступ к полям и методам вашей активности. Это хороший способ справиться с этой проблемой, с моей точки зрения.

person Mathias Seguy Android2ee    schedule 10.03.2016
comment
DialogFragments должен быть общедоступными статическими классами с общедоступным конструктором без операций, вот так просто. В противном случае система не сможет воссоздать фрагмент, если это необходимо в ситуации с нехваткой памяти и т. д. Кроме того, начиная с библиотеки поддержки v25, ваше приложение будет аварийно завершать работу при попытке отобразить DialogFragment, который не соответствует вышеупомянутому. ограничения: java.lang.IllegalStateException: Fragment TestActivity$TestDialogFrament must be a public static class to be properly recreated from instance state. - person JHH; 26.10.2016
comment
ой, опечатка: no-op -> no-arg - person JHH; 26.10.2016
comment
Нет. Не должны. Я могу воссоздать их самостоятельно. - person The incredible Jan; 07.07.2017
comment
Опять же, почему вы предпочитаете это в большинстве обычных ситуаций? - person JHH; 06.09.2017