アンドロイド。フラグメントgetActivity()がnullを返すことがある


194

開発者コンソールのエラーレポートで、NPEの問題があるレポートが表示されることがあります。コードの何が問題なのか理解できません。エミュレーターと私のデバイスアプリケーションはforceclosesなしで正常に動作しますが、一部のユーザーは、getActivity()メソッドが呼び出されたときにフラグメントクラスでNullPointerExceptionを受け取ります。

アクティビティ

pulic class MyActivity extends FragmentActivity{

    private ViewPager pager; 
    private TitlePageIndicator indicator;
    private TabsAdapter adapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        pager = (ViewPager) findViewById(R.id.pager);
        indicator = (TitlePageIndicator) findViewById(R.id.indicator);
        adapter = new TabsAdapter(getSupportFragmentManager(), false);

        adapter.addFragment(new FirstFragment());
        adapter.addFragment(new SecondFragment());
        indicator.notifyDataSetChanged();
        adapter.notifyDataSetChanged();

        // push first task
        FirstTask firstTask = new FirstTask(MyActivity.this);
        // set first fragment as listener
        firstTask.setTaskListener((TaskListener) adapter.getItem(0));
        firstTask.execute();
    }

    indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener()  {
        @Override
        public void onPageSelected(int position) {
            Fragment currentFragment = adapter.getItem(position);
            ((Taskable) currentFragment).executeTask();
        }

        @Override
        public void onPageScrolled(int i, float v, int i1) {}

        @Override
        public void onPageScrollStateChanged(int i) {}
    });
}

AsyncTaskクラス

public class FirstTask extends AsyncTask{

    private TaskListener taskListener;

    ...

    @Override
    protected void onPostExecute(T result) {
        ... 
        taskListener.onTaskComplete(result);
    }   
}

フラグメントクラス

public class FirstFragment extends Fragment immplements Taskable, TaskListener{

    public FirstFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.first_view, container, false);
    }

    @Override
    public void executeTask() {
        FirstTask firstTask = new FirstTask(MyActivity.this);
        firstTask.setTaskListener(this);
        firstTask.execute();
    }

    @Override
    public void onTaskComplete(T result) {
        // NPE is here 
        Resources res = getActivity().getResources();
        ...
    }
}

このエラーは、アプリケーションがバックグラウンドから再開したときに発生する可能性があります。この場合、どのようにこの状況を適切に処理する必要がありますか?


私は問題を理解しましたが、解決策ではありませんでした。理由はわかりませんが、フラグメントが以前のアクティビティを再開します。そして、これは最近リストされたアプリの最後の位置にあるアプリが最近発生した場合にのみ発生し、システムが私のアプリを破壊するようです。
Georgy Gobozov

1
アプリケーションをバックグラウンドfragmetn onCreateから再開すると、アクティビティonCreate / onResumeメソッドの前に呼び出されるonResumeが呼び出されます。いくつかの切り離されたフラグメントがまだ生きており、再開しようとしているようです。
Georgy Gobozov

1
この文字列のfirstTask.setTaskListener((TaskListener)adapter.getItem(0)); adapter.getItem(0)アダプタcorrectrlyフラグメントを除去していない、古いフラグメントを返す
ゲオルギーGobozov

9
ところで素晴らしい活動:)質問、コメント、回答が与えられました-すべてが一人で行われます!これらの+1。
Prizoff 2013年

Context(getActivity())をonCreateView()に保存します。これは、バックグラウンドでビューが再作成されるときに呼び出されるためです。
sha

回答:


123

問題の解決策を見つけたようです。ここここに非常に良い説明があります。これが私の例です:

pulic class MyActivity extends FragmentActivity{

private ViewPager pager; 
private TitlePageIndicator indicator;
private TabsAdapter adapter;
private Bundle savedInstanceState;

 @Override
public void onCreate(Bundle savedInstanceState) {

    .... 
    this.savedInstanceState = savedInstanceState;
    pager = (ViewPager) findViewById(R.id.pager);;
    indicator = (TitlePageIndicator) findViewById(R.id.indicator);
    adapter = new TabsAdapter(getSupportFragmentManager(), false);

    if (savedInstanceState == null){    
        adapter.addFragment(new FirstFragment());
        adapter.addFragment(new SecondFragment());
    }else{
        Integer  count  = savedInstanceState.getInt("tabsCount");
        String[] titles = savedInstanceState.getStringArray("titles");
        for (int i = 0; i < count; i++){
            adapter.addFragment(getFragment(i), titles[i]);
        }
    }


    indicator.notifyDataSetChanged();
    adapter.notifyDataSetChanged();

    // push first task
    FirstTask firstTask = new FirstTask(MyActivity.this);
    // set first fragment as listener
    firstTask.setTaskListener((TaskListener) getFragment(0));
    firstTask.execute();

}

private Fragment getFragment(int position){
     return savedInstanceState == null ? adapter.getItem(position) : getSupportFragmentManager().findFragmentByTag(getFragmentTag(position));
}

private String getFragmentTag(int position) {
    return "android:switcher:" + R.id.pager + ":" + position;
}

 @Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("tabsCount",      adapter.getCount());
    outState.putStringArray("titles", adapter.getTitles().toArray(new String[0]));
}

 indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageSelected(int position) {
            Fragment currentFragment = adapter.getItem(position);
            ((Taskable) currentFragment).executeTask();
        }

        @Override
        public void onPageScrolled(int i, float v, int i1) {}

        @Override
        public void onPageScrollStateChanged(int i) {}
 });

このコードの主なアイデアは、アプリケーションを通常どおり実行しているときに、新しいフラグメントを作成してアダプターに渡すことです。アプリケーションを再開するとき、フラグメントマネージャーにはすでにこのフラグメントのインスタンスがあり、フラグメントマネージャーから取得してアダプターに渡す必要があります。

更新

また、フラグメントを使用してgetActivity()が呼び出される前にisAddedを確認することもお勧めします。これにより、フラグメントがアクティビティから切り離されたときのnullポインタ例外を回避できます。たとえば、アクティビティには、非同期タスクをプッシュするフラグメントを含めることができます。タスクが完了すると、onTaskCompleteリスナーが呼び出されます。

@Override
public void onTaskComplete(List<Feed> result) {

    progress.setVisibility(View.GONE);
    progress.setIndeterminate(false);
    list.setVisibility(View.VISIBLE);

    if (isAdded()) {

        adapter = new FeedAdapter(getActivity(), R.layout.feed_item, result);
        list.setAdapter(adapter);
        adapter.notifyDataSetChanged();
    }

}

フラグメントを開いてタスクをプッシュし、すばやく押して前のアクティビティに戻ると、タスクが完了すると、getActivity()メソッドを呼び出してonPostExecute()のアクティビティにアクセスしようとします。アクティビティがすでに切り離されており、このチェックが存在しない場合:

if (isAdded()) 

その後、アプリケーションがクラッシュします。


56
しかし、これは面倒ですisAdded()。アクセスする前に呼び出す必要があるため、コードが見苦しくなります。
Ixx

25
if(isAdded())またはif(getActivity() != null)
StackOverflowed

19

わかりました、この質問は実際に解決されていることは知っていますが、これに対する解決策を共有することにしました。私の抽象親クラスを作成しましたFragment

public abstract class ABaseFragment extends Fragment{

    protected IActivityEnabledListener aeListener;

    protected interface IActivityEnabledListener{
        void onActivityEnabled(FragmentActivity activity);
    }

    protected void getAvailableActivity(IActivityEnabledListener listener){
        if (getActivity() == null){
            aeListener = listener;

        } else {
            listener.onActivityEnabled(getActivity());
        }
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        if (aeListener != null){
            aeListener.onActivityEnabled((FragmentActivity) activity);
            aeListener = null;
        }
    }

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

        if (aeListener != null){
            aeListener.onActivityEnabled((FragmentActivity) context);
            aeListener = null;
        }
    }
}

ご覧のとおり、リスナーを追加したので、Fragments Activity標準getActivity()ではなく取得する必要があるときはいつでも、

 getAvailableActivity(new IActivityEnabledListener() {
        @Override
        public void onActivityEnabled(FragmentActivity activity) {
            // Do manipulations with your activity
        }
    });

正解です。これは実際の問題を解決するため、正しいものとしてマークする必要があります。私の場合、getActivity()がnullでないことを確認するだけでは不十分です。何があってもタスクを完了する必要があるためです。これを使用していて、完全に機能します。
Hadas Kaminsky

18

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

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

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

onAttach(Activity)減価償却済みで現在onAttach(Context)使用されているため、編集済み


9
フラグメントは常に親アクティビティの参照を保持し、getActivity()メソッドを使用できるようにします。ここでは同じ参照を保持しています。
Pawan Maheshwari 2013

8
フラグメントを使用してイベントをアクティビティと共有する必要がある場合は、実際にこれをお勧めします。 developer.android.com/guide/components/fragments.html(「アクティビティへのイベントコールバックの作成」を探します)
Vering

6
アクティビティの参照を無効にするonDetachメソッドを追加することをお勧めします
深夜0

2
そう、onDetachメソッドでmActivity = nullを初期化して、そのアクティビティ参照を無効にします。
Pawan Maheshwari 2014年

19
決してそれをしないでください。あなたはあなたの完全なアクティビティをリークしています(そしてそれとともに、ドローアブルなどでレイアウトツリー全体をリークしています)。getActivity()nullが返された場合は、アクティビティに参加していないことが原因です。これは汚い回避策です。
njzk2 14

10

親アクティビティのonStartまで、getActivity()を必要とするFragment内のメソッドを呼び出さないでください。

private MyFragment myFragment;


public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);

    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    myFragment = new MyFragment();

    ft.add(android.R.id.content, youtubeListFragment).commit();

    //Other init calls
    //...
}


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

    //Call your Fragment functions that uses getActivity()
    myFragment.onPageSelected();
}

実際、フラグメントコンストラクターでタスクを開始していたため、同様の問題が発生しました。どうもありがとう。
Supreme Dolphin

4

私はこの種の問題にしばらく取り組んできましたが、信頼できる解決策を考え出したと思います。

特に、コードが参照を撤回するための十分な時間を与えるあらゆる種類のネットワーク動作を処理している場合は、それthis.getActivity()がで返さnullれないことを確認するのはかなり困難FragmentですActivity

以下のソリューションでは、と呼ばれる小さな管理クラスを宣言していActivityBufferます。基本的に、これclassは、所有するへの信頼できる参照を維持し、有効な参照が利用可能な場合は常に、有効なコンテキスト内でActivityを実行することを約束します。Sは、直ちに場合はUIスレッド上で実行するためにスケジュールされていることがまで、それ以外の場合は、実行が延期され、提供されて準備ができています。RunnableActivityRunnableContextContext

/** A class which maintains a list of transactions to occur when Context becomes available. */
public final class ActivityBuffer {

    /** A class which defines operations to execute once there's an available Context. */
    public interface IRunnable {
        /** Executes when there's an available Context. Ideally, will it operate immediately. */
        void run(final Activity pActivity);
    }

    /* Member Variables. */
    private       Activity        mActivity;
    private final List<IRunnable> mRunnables;

    /** Constructor. */
    public ActivityBuffer() {
        // Initialize Member Variables.
        this.mActivity  = null;
        this.mRunnables = new ArrayList<IRunnable>();
    }

    /** Executes the Runnable if there's an available Context. Otherwise, defers execution until it becomes available. */
    public final void safely(final IRunnable pRunnable) {
        // Synchronize along the current instance.
        synchronized(this) {
            // Do we have a context available?
            if(this.isContextAvailable()) {
                // Fetch the Activity.
                final Activity lActivity = this.getActivity();
                // Execute the Runnable along the Activity.
                lActivity.runOnUiThread(new Runnable() { @Override public final void run() { pRunnable.run(lActivity); } });
            }
            else {
                // Buffer the Runnable so that it's ready to receive a valid reference.
                this.getRunnables().add(pRunnable);
            }
        }
    }

    /** Called to inform the ActivityBuffer that there's an available Activity reference. */
    public final void onContextGained(final Activity pActivity) {
        // Synchronize along ourself.
        synchronized(this) {
            // Update the Activity reference.
            this.setActivity(pActivity);
            // Are there any Runnables awaiting execution?
            if(!this.getRunnables().isEmpty()) {
                // Iterate the Runnables.
                for(final IRunnable lRunnable : this.getRunnables()) {
                    // Execute the Runnable on the UI Thread.
                    pActivity.runOnUiThread(new Runnable() { @Override public final void run() {
                        // Execute the Runnable.
                        lRunnable.run(pActivity);
                    } });
                }
                // Empty the Runnables.
                this.getRunnables().clear();
            }
        }
    }

    /** Called to inform the ActivityBuffer that the Context has been lost. */
    public final void onContextLost() {
        // Synchronize along ourself.
        synchronized(this) {
            // Remove the Context reference.
            this.setActivity(null);
        }
    }

    /** Defines whether there's a safe Context available for the ActivityBuffer. */
    public final boolean isContextAvailable() {
        // Synchronize upon ourself.
        synchronized(this) {
            // Return the state of the Activity reference.
            return (this.getActivity() != null);
        }
    }

    /* Getters and Setters. */
    private final void setActivity(final Activity pActivity) {
        this.mActivity = pActivity;
    }

    private final Activity getActivity() {
        return this.mActivity;
    }

    private final List<IRunnable> getRunnables() {
        return this.mRunnables;
    }

}

その実装に関しては、ライフサイクルメソッドを適用して、Pawan Mによる上記の動作と一致するように注意する必要があります。

public class BaseFragment extends Fragment {

    /* Member Variables. */
    private ActivityBuffer mActivityBuffer;

    public BaseFragment() {
        // Implement the Parent.
        super();
        // Allocate the ActivityBuffer.
        this.mActivityBuffer = new ActivityBuffer();
    }

    @Override
    public final void onAttach(final Context pContext) {
        // Handle as usual.
        super.onAttach(pContext);
        // Is the Context an Activity?
        if(pContext instanceof Activity) {
            // Cast Accordingly.
            final Activity lActivity = (Activity)pContext;
            // Inform the ActivityBuffer.
            this.getActivityBuffer().onContextGained(lActivity);
        }
    }

    @Deprecated @Override
    public final void onAttach(final Activity pActivity) {
        // Handle as usual.
        super.onAttach(pActivity);
        // Inform the ActivityBuffer.
        this.getActivityBuffer().onContextGained(pActivity);
    }

    @Override
    public final void onDetach() {
        // Handle as usual.
        super.onDetach();
        // Inform the ActivityBuffer.
        this.getActivityBuffer().onContextLost();
    }

    /* Getters. */
    public final ActivityBuffer getActivityBuffer() {
        return this.mActivityBuffer;
    }

}

最後に、内の任意の領域では、あなたのFragment拡張BaseFragmentあなたがへの呼び出しについて信頼できないだとgetActivity()、単純に呼び出しを行うthis.getActivityBuffer().safely(...)と宣言しActivityBuffer.IRunnableたタスクのために!

その後、コンテンツはvoid run(final Activity pActivity)UIスレッドに沿って実行されることが保証されます。

ActivityBuffer次のように使用することができます。

this.getActivityBuffer().safely(
  new ActivityBuffer.IRunnable() {
    @Override public final void run(final Activity pActivity) {
       // Do something with guaranteed Context.
    }
  }
);

this.getActivityBuffer()。safely(...)メソッドの使用例を追加できますか?
fahad_sust

3
@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    // run the code making use of getActivity() from here
}

提供するソリューションについてもう少し説明を追加して、回答を詳しく説明していただけませんか?
abarisone 2016年

1

私はこれが古い質問であることを知っていますが、私の問題は他の人によって解決されなかったので、私はそれに答えを提供する必要があると思います。

まず第一に、私はfragmentTransactionsを使用してフラグメントを動的に追加していました。2番目:AsyncTasks(サーバー上のDBクエリ)を使用してフラグメントが変更されました。3番目:アクティビティの開始時にフラグメントがインスタンス化されませんでした4番目:フラグメント変数を取得するために、カスタムフラグメントのインスタンス化「作成またはロード」を使用しました。4番目:向きが変わったため、アクティビティが再作成されました

問題は、クエリの回答のためにフラグメントを「削除」したかったのですが、フラグメントが直前に誤って作成されていたということです。おそらく「コミット」が後で行われるためか、フラグメントを削除するときにフラグメントがまだ追加されていない理由はわかりません。したがって、getActivity()はnullを返していました。

解決策:1)新しいインスタンスを作成する前に、フラグメントの最初のインスタンスを正しく見つけようとしていることを確認する必要がありました2)そのフラグメントにserRetainInstance(true)を配置して、向きの変更を維持する必要がありました(バックスタックなし)したがって、問題はありません)3)「削除」の直前に「古いフラグメントを再作成または取得する」のではなく、アクティビティの開始時にフラグメントを直接配置します。削除する前にフラグメント変数を「ロード」(またはインスタンス化)する代わりに、アクティビティの開始時にインスタンス化すると、getActivityの問題が回避されました。


0

Kotlinでは、この方法でgetActivity()null条件を処理できます。

   activity.let { // activity == getActivity() in java

        //your code here

   }

アクティビティがnullかどうかをチェックし、nullでない場合は内部コードを実行します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.