web-dev-qa-db-ja.com

getActivity()はFragment関数でnullを返します

私はこのようなパブリックメソッドを持つフラグメント(F1)を持っています

public void asd() {
    if (getActivity() == null) {
        Log.d("yes","it is null");
    }
}

はい(アクティビティから)呼び出すと、nullになります...

FragmentTransaction transaction1 = getSupportFragmentManager().beginTransaction();
F1 f1 = new F1();
transaction1.replace(R.id.upperPart, f1);
transaction1.commit();
f1.asd();

それは私が非常に間違っていることでなければなりませんが、それが何であるかはわかりません

168
Lukap

commitはトランザクションをスケジュールします。つまり、すぐには実行されませんが、メインスレッドの次回の準備時にメインスレッドでの作業としてスケジュールされます。

追加することをお勧めします

onAttach(Activity activity)

メソッドをFragmentに追加し、ブレークポイントを設定して、asd()への呼び出しに対していつ呼び出されるかを確認します。 asd()を呼び出したメソッドが終了した後に呼び出されることがわかります。 onAttach呼び出しは、Fragmentがそのアクティビティにアタッチされる場所であり、この時点からgetActivity()はnull以外を返します(nbはonDetach()呼び出しもあります)。

153
PJL

これを取り除く最善の方法は、onAttachが呼び出されたときにアクティビティ参照を保持し、必要に応じてアクティビティ参照を使用することです。

@Override
public void onAttach(Context context) {
    super.onAttach(activity);
    mContext = context;
}

@Override
public void onDetach() {
    super.onDetach();
    mContext = null;
}
83

これは、フラグメントが削除された後に終了した別のスレッドでgetActivity()を呼び出したときに発生しました。典型的なケースは、HTTPリクエストが終了したときにgetActivity()(たとえばToastの場合)を呼び出すことです(たとえばonResponseで)。

これを回避するには、フィールド名mActivityを定義し、getActivity()の代わりに使用できます。このフィールドは、次のようにFragmentのonAttach()メソッドで初期化できます。

@Override
public void onAttach(Context context) {
    super.onAttach(context);

    if (context instanceof Activity){
        mActivity =(Activity) context;
    }
}

私のプロジェクトでは、通常、この機能を使用してすべてのフラグメントの基本クラスを定義します。

public abstract class BaseFragment extends Fragment {

    protected FragmentActivity mActivity;

    @Override
public void onAttach(Context context) {
    super.onAttach(context);

    if (context instanceof Activity){
        mActivity =(Activity) context;
    }
}
}

ハッピーコーディング、

73
thucnguyen

Android AP​​Iレベル23以降、onAttach(Activityアクティビティ)は非推奨になりました。 onAttach(Context context)を使用する必要があります。 http://developer.Android.com/reference/Android/app/Fragment.html#onAttach(Android.app.Activity)

アクティビティはコンテキストであるため、単純にコンテキストがアクティビティであることを確認し、必要に応じてキャストすることができます。

@Override
public void onAttach(Context context) {
    super.onAttach(context);

    Activity a;

    if (context instanceof Activity){
        a=(Activity) context;
    }

}
16
Sachin

OnAttachのアクティビティへの参照を維持することを示唆する他の回答は、実際の問題に対する絆創膏を示唆しているだけです。 getActivityがnullを返す場合、フラグメントがアクティビティに添付されていないことを意味します。ほとんどの場合、これはアクティビティが回転または終了したためにアクティビティがなくなったときに発生しますが、フラグメントには何らかのコールバックリスナーがあります。アクティビティで何かをする必要があるがリスナーがなくなった場合にリスナーが呼び出されると、できることはあまりありません。コードでgetActivity() != nullを確認する必要があり、ない場合は何もしません。失われたアクティビティへの参照を保持している場合、アクティビティがガベージコレクションされないようにしています。あなたがしようとするかもしれないUIのことは、ユーザーには見えません。コールバックリスナーで、UIに関連しない何かのコンテキストが必要な状況を想像できます。そのような場合、おそらくアプリケーションコンテキストを取得する方が理にかなっています。 onAttachトリックが大きなメモリリークではない唯一の理由は、通常、コールバックリスナーの実行後は不要になり、フラグメント、そのすべてのビュー、およびアクティビティコンテキストと共にガベージコレクションできるためです。 setRetainInstance(true)を使用すると、Activityフィールドも保持されるため、メモリリークが発生する可能性が高くなりますが、ローテーション後は現在のアクティビティではなく前のアクティビティになる可能性があります。

11
miguel

PJLは正しい。私は彼の提案を使用しましたが、これは私がやったことです:

  1. フラグメントの定義済みグローバル変数:

    private final Object attachingActivityLock = new Object();

    private boolean syncVariable = false;

  2. 実装済み

@Override
public void onAttach(Activity activity) {
  super.onAttach(activity);
  synchronized (attachingActivityLock) {
      syncVariable = true;
      attachingActivityLock.notifyAll();
  }
}

3。メインスレッドで実行する場合、ステップ4でスレッドをブロックし、onAttach()は呼び出されないため、getActivity()をスレッドで呼び出す必要がある場所で関数をラップしました。

    Thread processImage = new Thread(new Runnable() {

        @Override
        public void run() {
            processImage();
        }
    });
    processImage.start();

4。 getActivity()を呼び出す必要がある関数で、これを使用します(getActivity()を呼び出す前)

    synchronized (attachingActivityLock) {
        while(!syncVariable){
            try {
                attachingActivityLock.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

Iの更新がある場合は、UIスレッドで実行することを忘れないでください。ImgeViewを更新する必要があるので、次のようにしました:

image.post(new Runnable() {

    @Override
    public void run() {
        image.setImageBitmap(imageToShow);
    }
});
10
zajac.m2

Commit()の後にコールバックが呼び出される順序:

  1. Commit()の直後に手動で呼び出すメソッド
  2. onAttach()
  3. onCreateView()
  4. onActivityCreated()

いくつかのビューを含む作業を行う必要があったため、onAttach()は機能しませんでした。クラッシュしました。そのため、commit()(1.)の直後に呼び出されるメソッド内にいくつかのparamsを設定するコードの一部を移動し、onCreateView()(3.)内のビューを処理するコードの他の部分を移動しました。

7
Bogdan Zurac

OkHttpを使用していますが、この問題に直面しました。


最初の部分については、 @ thucnguyenは正しい方向にありました

これは、フラグメントが削除された後に終了した別のスレッドでgetActivity()を呼び出すと発生しました。典型的なケースは、HTTPリクエストが終了したときにgetActivity()(例:Toast)を呼び出すことです(たとえばonResponseで)。

一部のHTTP呼び出しは、アクティビティが閉じられた後でも実行されていました(HTTP要求が完了するまで時間がかかることがあるため)。その後、HttpCallbackを介していくつかのFragmentフィールドを更新しようとし、getActivity()を実行しようとするとnull例外が発生しました。

http.newCall(request).enqueue(new Callback(...
  onResponse(Call call, Response response) {
    ...
    getActivity().runOnUiThread(...) // <-- getActivity() was null when it had been destroyed already

IMOの解決策は、フラグメントがもはや生きていないときにコールバックが発生するのを防ぐことです(そしてそれはOkhttpだけではありません)。

修正:防止。

fragment lifecycle (詳細情報 here )を見ると、onAttach(Context context)およびonDetach()メソッドがあることに気付くでしょう。これらは、フラグメントがアクティビティに属している後、そうなるのをやめる直前に呼び出されます。

つまり、onDetachメソッドでコールバックを制御することで、そのコールバックが発生するのを防ぐことができます。

@Override
public void onAttach(Context context) {
    super.onAttach(context);

    // Initialize HTTP we're going to use later.
    http = new OkHttpClient.Builder().build();
}

@Override
public void onDetach() {
    super.onDetach();

    // We don't want to receive any more information about the current HTTP calls after this point.
    // With Okhttp we can simply cancel the on-going ones (credits to https://github.com/square/okhttp/issues/2205#issuecomment-169363942).
    for (Call call : http.dispatcher().queuedCalls()) {
        call.cancel();
    }
    for (Call call : http.dispatcher().runningCalls()) {
        call.cancel();
    }
}
2
zurfyx

次のようにします。役に立つと思います。

private boolean isVisibleToUser = false;
private boolean isExecutedOnce = false;


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View root = inflater.inflate(R.layout.fragment_my, container, false);
    if (isVisibleToUser && !isExecutedOnce) {
        executeWithActivity(getActivity());
    }
    return root;
}

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    this.isVisibleToUser = isVisibleToUser;
    if (isVisibleToUser && getActivity()!=null) {
        isExecutedOnce =true;
        executeWithActivity(getActivity());
    }
}


private void executeWithActivity(Activity activity){
    //Do what you have to do when page is loaded with activity

}
1
Vinil Chandran

この関数はどこで呼び出しますか? Fragmentのコンストラクターで呼び出すと、nullが返されます。

メソッドgetActivity()が実行されたときにonCreateView()を呼び出すだけです。

1
Phạm Lam

まだonAttach(Activityアクティビティ)に問題がある人は、コンテキストに変更しました-

    @Override
public void onAttach(Context context) {
    super.onAttach(context);
    this.context = context;
}

ほとんどの場合、コンテキストを保存するだけで十分です。たとえば、getResources()を実行する場合は、コンテキストから直接実行できます。アクティビティにコンテキストを作成する必要がある場合は、そうします-

 @Override
public void onAttach(Context context) {
    super.onAttach(context);
    mActivity a; //Your activity class - will probably be a global var.
    if (context instanceof mActivity){
        a=(mActivity) context;
    }
}

User1868713によって提案されたとおり。

0
Shai

OnAttachを使用するか、どこにでもonAttachを配置したくない場合は、メインのAppクラスにApplicationContextを返すメソッドを配置できます。

public class App {
    ...  
    private static Context context;

    @Override
    public void onCreate() {
        super.onCreate();
        context = this;
    }

    public static Context getContext() {
        return context;
    }
    ...
}

その後、次のようにプロジェクト全体でどこでも再利用できます:

App.getContext().getString(id)

これがうまくいかない場合はお知らせください。

0
surga

別の良い解決策は、MVVMアーキテクチャでAndroidのLiveDataを使用することです。 ViewModel内でLiveDataオブジェクトを定義し、フラグメントでそれを観察します。LiveDataの値が変更されると、フラグメントがアクティブな状態にある場合にのみオブザーバー(この場合はフラグメント)に通知します。フラグメントがアクティブ状態の場合にのみ、UIが機能し、アクティビティにアクセスします。これは、 LiveData の利点の1つです。

もちろん、この質問が最初に尋ねられたとき、LiveDataはありませんでした。私が見るように、この問題がまだあり、誰かに役立つ可能性があるため、ここにこの答えを残しています。