studio - Android. Fragmento getActivity() às vezes retorna nulo



lifecycleowner in fragment (5)

Eu sei que esta é uma pergunta antiga, mas eu acho que devo fornecer a minha resposta, porque o meu problema não foi resolvido por outros.

Primeiro de tudo: eu estava adicionando dinamicamente fragmentos usando fragmentTransactions. Segundo: meus fragmentos foram modificados usando o AsyncTasks (consultas do banco de dados em um servidor). Terceiro: meu fragmento não foi instanciado no início da atividade Quarto: usei uma instanciação de fragmento personalizada "crie ou carregue" para obter a variável de fragmento. Quarto: a atividade foi recriada por causa da mudança de orientação

O problema era que eu queria "remover" o fragmento por causa da resposta da consulta, mas o fragmento foi criado incorretamente antes. Eu não sei porque, provavelmente por causa do "commit" ser feito depois, o fragmento não foi adicionado ainda na hora de removê-lo. Portanto getActivity () estava retornando null.

Solução: 1) Eu tinha que verificar se estava tentando encontrar corretamente a primeira instância do fragmento antes de criar um novo. 2) Eu tive que colocar serRetainInstance (true) nesse fragmento para mantê-lo através da mudança de orientação (sem backstack necessário, portanto, não há problema) 3) Em vez de "recriar ou obter fragmento de idade" pouco antes de "removê-lo", eu coloquei diretamente o fragmento no início da atividade. Instanciando-o no início da atividade, em vez de "carregar" (ou instanciar) a variável de fragmento antes de removê-la, evitou problemas getActivity.

https://code.i-harness.com

Nos relatórios de erro do console do desenvolvedor, às vezes, vejo relatórios com problema de NPE. Eu não entendo o que está errado com o meu código. No emulador e meu aplicativo de dispositivo funciona bem sem forcecloses, no entanto, alguns usuários recebem NullPointerException na classe de fragmento quando o método getActivity () é chamado.

Atividade

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

Classe AsyncTask

public class FirstTask extends AsyncTask{

    private TaskListener taskListener;

    ...

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

Classe fragmento

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

Talvez esse erro aconteça quando os aplicativos forem retomados do segundo plano. Neste caso, como eu deveria lidar com essa situação corretamente?


Eu tenho lutado contra esse tipo de problema por um tempo, e acho que encontrei uma solução confiável.

É muito difícil saber com certeza que this.getActivity() não retornará null para um Fragment , especialmente se você estiver lidando com qualquer tipo de comportamento de rede que forneça a seu código tempo suficiente para retirar referências de Activity .

Na solução abaixo, eu declaro uma pequena classe de gerenciamento chamada ActivityBuffer . Essencialmente, esta class lida com a manutenção de uma referência confiável para uma Activity proprietária, e promete executar Runnable s dentro de um contexto de Activity válido sempre que houver uma referência válida disponível. Os Runnable s estão programados para execução no Thread de UI imediatamente se o Context estiver disponível, caso contrário, a execução é adiada até que o Context esteja pronto.

/** 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;
    }

}

Em termos de sua implementação, devemos ter o cuidado de aplicar os métodos do ciclo de vida para coincidir com o comportamento descrito acima por 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;
    }

}

Finalmente, em qualquer área dentro do seu Fragment que estenda o BaseFragment que você não é confiável sobre uma chamada para getActivity() , simplesmente faça uma chamada para this.getActivityBuffer().safely(...) e declare um ActivityBuffer.IRunnable para a tarefa. !

O conteúdo do seu void run(final Activity pActivity) é garantido para executar ao longo do thread de interface do usuário.


O melhor para se livrar disso é manter a referência de atividade quando onAttach é chamado e usar a referência de atividade sempre que necessário, por exemplo,

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

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

Editado, desde que onAttach(Activity) foi depreciado e agora onAttach(Context) está sendo usado


Ok, eu sei que esta questão está realmente resolvida, mas decidi compartilhar minha solução para isso. Eu criei a classe pai abstrata para o meu 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;
        }
    }
}

Como você pode ver, eu adicionei um ouvinte assim, sempre que eu precisar pegar Fragments Activity invés do padrão getActivity() , eu precisarei chamar

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

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




nullpointerexception