内部的DialogFragment类应该是静态的还是非静态的?

10

这是我从我的项目中提取出来的一小段代码,我正在用它来学习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;
3个回答

20

这个示例没有那些注释,因为该类在自己的文件中。这意味着它是独立于使用 Fragment 的 Activity 的。

在您的情况下,您的 Fragment 在 Activity 中且未使用 static 修饰符。这意味着它与 Activity 实例绑定。

使 Fragment 依赖于 Activity 实例是一个坏主意,因为这两个类都有复杂的生命周期(特别是由于 Activities 经常被销毁和重新创建),并且应相互独立。

您需要将 EnableGpsDialogFragment 的修饰符更改为 static

public static class EnableGpsDialogFragment extends DialogFragment {

一个 static 类不依赖于封闭类的实例,因此警告将消失。

要获取更多信息,请阅读Java教程关于 嵌套类的部分。

根据您的编辑进行编辑: 现在类不再依赖彼此的实例,因此您必须获取 YourActivity 的实例,以便调用 enabledLocationSettings()。一种方法是通过转换,只有当 EnableGpsDialogFragment 仅由 YourActivity 使用时才起作用:

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

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

如果这个Fragment将被多个Activity使用,你应该创建一个接口(interface)并由每个Activity实现它


谢谢,但是将static修饰符添加到EnableGpsDialogFragment会强制我将static修饰符添加到private void enableLocationSettings()。如果我这样做,会出现一个新的错误:无法从Activity类型中对非静态方法startActivity(Intent)进行静态引用。 - szpic
@szpic,那是因为你现在对问题进行的编辑包括调用enableLocationSettings(); - A--C
为了避免这种情况,您可以使用一个弱引用来引用活动中所需的东西。在使用之前,请检查它是否仍然有效。为了确保其有效性,应该在每次销毁/创建活动时进行设置。关于静态片段,需要注意的是:如果不以静态方式创建它们,则可能会在此上下文中导致内存泄漏,因为它们可能会持有对旧活动的引用,而这些活动已经无法访问并且应该被垃圾回收。 - Zachary Moshansky
1
谢谢你救了我的一天。我希望我能给你更多的赞! - Ale

4
所有的DialogFragments都应该是public的,如果是内部类,也应该是static的。它们还应该有一个公共的无参数构造函数,并且仅依赖于setArguments()来传递参数。
如果不遵守这个规则,就会产生一个Lint警告,你可以选择忽略它,但从Android支持库v25开始,如果尝试显示不符合这些规则的DialogFragment,你将实际上会得到一个异常:
java.lang.IllegalStateException: Fragment TestActivity$TestDialogFrament must be a public static class to be properly recreated from instance state.
原因是,正如所述,操作系统必须能够在某些情况下重新创建所有片段,例如在将应用程序放入后台时强制销毁片段的低内存情况下。当应用程序再次进入前台时,片段应该能够从序列化的应用程序状态重新创建,对于非静态内部类而言,除了从外部类的实例中,无法从那个上下文中重新创建。
不幸的是,这使得对话框更加复杂,因为通常很方便只需创建一个匿名子类,覆盖onCreateDialog即可。然而,这样的对话框根本无法重新创建。

低内存情况是一个真正的威胁,因为我们在廉价智能手机中至少有2GB的RAM... :) 我从来没有理解过为什么Google想要重新创建我的对话框片段,如果他们必须销毁它们-我可以自己做到这一点,并且已经这样做了(我使用v23并且不会升级)。 - The incredible Jan
部分原因是因为许多应用程序表现不佳,基本上一直在运行,或者由于频繁的广播意图而不断重新启动。这在Android N和O中得到了严格解决。如果有50个应用程序想一直运行,那么将会有很多应用程序从内存中进出交换。此外,请记住,在1080p屏幕上,一个单独的位图就有8 MB。我不太确定为什么您想要完全手动重新创建状态,但我们都有自己的习惯... - JHH

1

不应该这样做!

从我的角度来看,我不希望我的DialogFragment(你的NetworkConnectionError)是静态的,因为我想能够从中调用包含类(Activity)的变量或方法。
所以它不会是静态的。 但我也不想产生内存泄漏。
那么解决方案是什么?
简单,在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");
}

这样做可以避免内存泄漏(因为它很糟糕),并确保您没有一个无法访问活动字段和方法的静态片段。

从我的角度来看,这是处理该问题的正确方式。


DialogFragments应该是公共静态类,具有公共无操作构造函数,就这么简单。如果不是这样,系统将无法在低内存情况下重新创建片段等。此外,从支持库v25开始,如果尝试显示不满足上述约束条件的DialogFragment,则应用程序将崩溃:java.lang.IllegalStateException: Fragment TestActivity$TestDialogFrament必须是公共静态类,以便从实例状态正确地重新创建。 - JHH
翻译:糟糕,打错字了:no-op -> no-arg - JHH
不用,它们不需要。我可以自己重新创建它们。 - The incredible Jan
在大多数正常情况下,你为什么更喜欢那个呢? - JHH

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接