web-dev-qa-db-ja.com

フラグメント内部クラスは静的である必要があります

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()はAsynctaskを開始します。

69
user2176737

非静的内部クラスは、親クラスへの参照を保持します。 Fragment内部クラスを非静的にすることの問題は、常にActivityへの参照を保持することです。 GarbageCollectorは、Activityを収集できません。したがって、たとえば方向が変わった場合、Activityを「リーク」することができます。 Fragmentはまだ有効であり、新しいActivityに挿入されるためです。

編集:

何人かの人々が私にいくつかの例を尋ねてきたので、私はそれを書き始めましたが、これをしている間に、非静的なフラグメントを使用するときにいくつかの問題を見つけました:

  • それらは空のコンストラクターがないため、xmlファイルでは使用できません(空のコンストラクターを持つことができますが、通常はmyActivityInstance.new Fragment()を実行することで非静的なネストされたクラスをインスタンス化します。 )
  • FragmentManagerがこの空のコンストラクターを呼び出すこともあるため、これらはまったく再利用できません。一部のトランザクションでFragmentを追加した場合。

したがって、私の例を動作させるために、

wrongFragment.setRetainInstance(true);

方向の変更時にアプリをクラッシュさせないための行。

このコードを実行すると、テキストビューと2つのボタンでアクティビティが発生します。ボタンはカウンターを増やします。そして、フラグメントは、彼らの活動が持っていると思うオリエンテーションを示しています。最初はすべて正常に動作します。しかし、画面の向きを変更した後、最初のフラグメントのみが正しく機能します。2番目のフラグメントは、古いアクティビティでまだ呼び出しを行っています。

私のアクティビティクラス:

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();
}

}

異なるアクティビティでFragmentを使用する場合は、おそらく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>
93
Simon Meyer

内部フラグメントについては説明しませんが、より具体的にはアクティビティ内で定義されたDialogFragmentについて説明します。これはこの質問の99%のケースだからです。
私の観点から、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");
}

そして、この方法でメモリリークを回避し(悪いため)、アクティビティのフィールドとメソッドにアクセスできない[expい]静的フラグメントがないことを保証します。これは、私の観点からその問題を処理するための良い方法です。

U Android studioで開発した場合、staticとして指定しなくても問題ありません。プロジェクトはエラーなしで実行され、apkの生成時にError:This fragment innerが発生します。クラスは静的でなければなりません[ValidFragment]

それはlintエラーです。おそらくgradleでビルドしています。エラー時のアボートを無効にするには、次を追加します:

lintOptions {
    abortOnError false
}

to build.gradle。 `

5
chakri Reddy

外部クラス(Activity)のメンバーにアクセスしたいが、Activityでメンバーを静的にしたくない場合(フラグメントはpublic staticである必要があるため)、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
        ...
     }
4
Deepu