片段内部类应该是静态的。

70

我有一个包含内部类的FragmentActivity类,该类应该显示Dialog。但我被要求将其设置为static。Eclipse建议我使用@SuppressLint("ValidFragment")来消除错误。如果我这样做,这是否是不好的编程风格,可能会有什么后果?

public class CarActivity extends FragmentActivity {
//Code
  @SuppressLint("ValidFragment")
  public class NetworkConnectionError extends DialogFragment {
    private String message;
    private AsyncTask task;
    private String taskMessage;
    @Override
    public void setArguments(Bundle args) {
      super.setArguments(args);
      message = args.getString("message");
    }
    public void setTask(CarActivity.CarInfo task, String msg) {
      this.task = task;
      this.taskMessage = msg;
    }
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
      // Use the Builder class for convenient dialog construction
      AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
      builder.setMessage(message).setPositiveButton("Go back", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int id) {
          Intent i = new Intent(getActivity().getBaseContext(), MainScreen.class);
          startActivity(i);
        }
      });
      builder.setNegativeButton("Retry", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int id) {
          startDownload();
        }
      });
      // Create the AlertDialog object and return it
      return builder.create();
    }
  }

startDownload() 开始异步任务。


2
一般来说,忽略代码检查是不好的做法。这是一种相当智能的工具。 试着发布你的代码,以便获得更好的改进建议。 - Stefan de Bruijn
你有没有查看过 http://code.google.com/p/android/issues/detail?id=41800,以了解 ValidFragment 是什么?lint提示说:“每个 fragment 必须有一个空构造函数,以便实例化”。 - sandrstar
我已经这样做了。但我不明白为什么我不能忽略这个警告。可能会有什么后果? - user2176737
5个回答

94

非静态内部类会持有对其父类的引用。将Fragment内部类设置为非静态的问题在于,您始终会持有对Activity的引用。 GarbageCollector无法回收您的Activity,因此如果发生方向更改等情况,则可能会'泄漏'该Activity。因为Fragment仍然可以存在并且插入到新的Activity中。

编辑:

由于一些人要求我提供示例,因此我开始编写一个示例,但在此过程中,我发现在使用非静态Fragment时还存在更多问题:

  • 它们不能在xml文件中使用,因为它们没有空构造函数(它们可以有空构造函数,但通常通过执行myActivityInstance.new Fragment()来实例化非静态嵌套类,这与仅调用空构造函数不同)
  • 它们根本无法重复使用-因为FragmentManager有时也会调用此空构造函数。如果您在某个事务中添加了Fragment

因此,为了使我的示例起作用,我不得不添加

wrongFragment.setRetainInstance(true);

避免应用程序在方向更改时崩溃的代码。

如果您执行此代码,您将获得一个带有一些文本视图和2个按钮的活动 - 这些按钮增加了一些计数器。并且Fragment会显示他们认为自己所在的活动方向。 起初,一切都正常工作。 但是在更改屏幕方向后,只有第一个Fragment正常工作 - 第二个仍在调用其旧活动的东西。

我的Activity类:

package com.example.fragmenttest;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;

public class WrongFragmentUsageActivity extends Activity
{
private String mActivityOrientation="";
private int mButtonClicks=0;
private TextView mClickTextView;


private static final String WRONG_FRAGMENT_TAG = "WrongFragment" ;

@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    int orientation = getResources().getConfiguration().orientation;
    if (orientation == Configuration.ORIENTATION_LANDSCAPE)
    {
        mActivityOrientation = "Landscape";
    }
    else if (orientation == Configuration.ORIENTATION_PORTRAIT)
    {
        mActivityOrientation = "Portrait";
    }

    setContentView(R.layout.activity_wrong_fragement_usage);
    mClickTextView = (TextView) findViewById(R.id.clicksText);
    updateClickTextView();
    TextView orientationtextView = (TextView) findViewById(R.id.orientationText);
    orientationtextView.setText("Activity orientation is: " + mActivityOrientation);

    Fragment wrongFragment = (WrongFragment) getFragmentManager().findFragmentByTag(WRONG_FRAGMENT_TAG);
    if (wrongFragment == null)
    {
        wrongFragment = new WrongFragment();
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.add(R.id.mainView, wrongFragment, WRONG_FRAGMENT_TAG);
        ft.commit();
        wrongFragment.setRetainInstance(true); // <-- this is important - otherwise the fragment manager will crash when readding the fragment
    }
}

private void updateClickTextView()
{
    mClickTextView.setText("The buttons have been pressed " + mButtonClicks + " times");
}

private String getActivityOrientationString()
{
    return mActivityOrientation;
}


@SuppressLint("ValidFragment")
public class WrongFragment extends Fragment
{


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        LinearLayout result = new LinearLayout(WrongFragmentUsageActivity.this);
        result.setOrientation(LinearLayout.VERTICAL);
        Button b = new Button(WrongFragmentUsageActivity.this);
        b.setText("WrongFragmentButton");
        result.addView(b);
        b.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                buttonPressed();
            }
        });
        TextView orientationText = new TextView(WrongFragmentUsageActivity.this);
        orientationText.setText("WrongFragment Activities Orientation: " + getActivityOrientationString());
        result.addView(orientationText);
        return result;
    }
}

public static class CorrectFragment extends Fragment
{
    private WrongFragmentUsageActivity mActivity;


    @Override
    public void onAttach(Activity activity)
    {
        if (activity instanceof WrongFragmentUsageActivity)
        {
            mActivity = (WrongFragmentUsageActivity) activity;
        }
        super.onAttach(activity);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        LinearLayout result = new LinearLayout(mActivity);
        result.setOrientation(LinearLayout.VERTICAL);
        Button b = new Button(mActivity);
        b.setText("CorrectFragmentButton");
        result.addView(b);
        b.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                mActivity.buttonPressed();
            }
        });
        TextView orientationText = new TextView(mActivity);
        orientationText.setText("CorrectFragment Activities Orientation: " + mActivity.getActivityOrientationString());
        result.addView(orientationText);
        return result;
    }
}

public void buttonPressed()
{
    mButtonClicks++;
    updateClickTextView();
}

}

请注意,如果您想在不同的活动中使用片段,则可能不应该在onAttach中转换活动 - 但对于这个例子,它可以工作。

activity_wrong_fragement_usage.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".WrongFragmentUsageActivity" 
android:id="@+id/mainView">

<TextView
    android:id="@+id/orientationText"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="" />

<TextView
    android:id="@+id/clicksText"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="" />



<fragment class="com.example.fragmenttest.WrongFragmentUsageActivity$CorrectFragment"
          android:id="@+id/correctfragment"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content" />


</LinearLayout>

非常有趣且可能非常有用的答案。我正在处理相同的问题。您介意提供一些您答案所基于的来源吗? - Egis
3
@Egis,也许这篇文章能帮你更好地了解Java中的静态嵌套内部类:http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html。 - AxeEffect
1
你有这个答案的参考资料吗? - Mike T
我认为WrongFragment实例保持对旧Activity实例的引用(在onCreateView()中使用WrongFragmentActivity.this),是因为WrongFragment.onCreateView()没有再次调用;由于setRetainInstance(true)和通过findFragmentByTag()“保留”片段,视图永远不会重新创建,而是通过XML布局文件fragment元素获取CorrectFragment的新实例。这是Android架构的一个小问题和副产品,但值得理解。 - MeanderingCode
onAttach 已经废弃了。 - trans

18

我不会谈论内部片段,但更具体地说,是关于在活动中定义的DialogFragment,因为这是此问题的99%。
从我的角度来看,我不希望我的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");
}

这样可以避免内存泄漏(因为它很糟糕),并确保您不会有一个[令人讨厌的]静态片段无法访问您的活动字段和方法。
从我的角度来看,这是解决该问题的好方法。


看起来不错,但是在生成apk的时候会像@hakri Reddy所提到的那样出现错误吗? - Shirish Herwade
是的,但这并不是因为lint不够聪明,我们需要“像它一样愚蠢”,使用这种技术可以消除内存泄漏(CanaryLeak会向您展示)...顺便说一句,我首先使用lint运行我的apk以检测我在代码中可能犯的其他错误,修复我认为需要修复的问题,然后使用abortOnError false运行它。在某些项目中,我会针对这个特定规则(“内部类应该是静态的”降为弱警告)自定义Lint。 - Mathias Seguy Android2ee
哦...但在我的情况下,实际的apk生成是由位于美国办公室的不同团队完成的(我在印度,只提供git代码存储库链接),因为他们不会向任何人共享公司签名证书文件。所以他们肯定不会听我的理由,也不会改变他们的设置 :( - Shirish Herwade
是的,有时候不仅仅是技术上的限制 :) 我们必须接受它们 :) - Mathias Seguy Android2ee
这种方法给我带来了一个“片段必须是公共静态类崩溃错误”的问题。 - leonardkraemer

5

如果您在Android Studio中开发它,那么不必担心,即使不将其设置为静态,该项目也会正常运行。在生成APK的时候,您会遇到错误:This fragment inner class should be static [ValidFragment]

这是Lint出现的错误,您可能正在使用Gradle进行构建,要禁用错误中止,请添加以下内容:

lintOptions {
    abortOnError false
}

添加到build.gradle文件中。


8
没错,它可以通过构建过程,但这样使用对吗?因为存在内存泄漏问题,这就是为什么Android Studio会发出警告的原因。 - XcodeNOOB
有时我觉得将abortOnError设置为false太困难了,我更喜欢将lint规则“内部类应该是静态的”定制为weakwarning或info。 - Mathias Seguy Android2ee

5

如果你想访问外部类(Activity)的成员,但又不想在Activity中使成员变为静态(因为片段应该是公共静态的),可以在onActivityCreated上进行覆盖。

public static class MyFragment extends ListFragment {

    private OuterActivityName activity; // outer Activity

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        activity = (OuterActivityName) getActivity();
        ...
        activity.member // accessing the members of activity
        ...
     }

-2
在内部类之前添加注释
@SuppressLint("validFragment")

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