Android. O fragmento getActivity () às vezes retorna nulo

194

Nos relatórios de erro do console do desenvolvedor, às vezes vejo relatórios com problemas de NPE. Não entendo o que há de errado com meu código. No emulador e meu aplicativo de dispositivo funciona bem sem execuções hipotecárias, no entanto, alguns usuários obtêm 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 de 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 ocorra quando os aplicativos forem retomados em segundo plano. Nesse caso, como devo lidar com essa situação corretamente?

Georgy Gobozov
fonte
Eu descobri um problema, mas não solução. Não sei por que, mas o fragmento retoma a atividade anterior. E isso só acontece quando meu aplicativo na última posição na lista de aplicativos recentes, parece que o sistema destrói meu aplicativo.
Georgy Gobozov
1
Quando retomo meu aplicativo do fragmetn em segundo plano, onCreate, um onResume chamado antes da atividade, no método onCreate / onResume. Parece algum fragmento desanexado ainda vivo e tentando retomar.
Georgy Gobozov
1
nesta cadeia firstTask.setTaskListener ((TaskListener) adapter.getItem (0)); adapter.getItem (0) retorna fragmento antigo, o adaptador não remove os fragmentos corretamente
Georgy Gobozov
9
Ótima atividade a propósito :) perguntas feitas, comentários deixados e respostas dadas - todos são feitos por uma única pessoa! +1 para estes.
Prizoff
salve o Contexto (getActivity ()) em onCreateView (), pois isso é chamado quando a exibição é recriada no caso em segundo plano.
sha

Respostas:

123

Parece que encontrei uma solução para o meu problema. Muito boas explicações são dadas aqui e aqui . Aqui está o meu exemplo:

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

A idéia principal desse código é que, enquanto você executa o aplicativo normalmente, você cria novos fragmentos e os passa para o adaptador. Quando você está reiniciando, o gerenciador de fragmentos de aplicativos já possui a instância desse fragmento e é necessário obtê-lo do gerenciador de fragmentos e transmiti-lo ao adaptador.

ATUALIZAR

Além disso, é uma boa prática ao usar fragmentos para verificar isAdded antes de getActivity () ser chamado. Isso ajuda a evitar uma exceção de ponteiro nulo quando o fragmento é desanexado da atividade. Por exemplo, uma atividade pode conter um fragmento que envia uma tarefa assíncrona. Quando a tarefa é concluída, o ouvinte onTaskComplete é chamado.

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

}

Se abrirmos o fragmento, pressionar uma tarefa e pressionar rapidamente para retornar à atividade anterior, quando a tarefa for concluída, ela tentará acessar a atividade em onPostExecute () chamando o método getActivity (). Se a atividade já estiver desanexada e esta verificação não estiver lá:

if (isAdded()) 

então o aplicativo trava.

Georgy Gobozov
fonte
56
Isso é irritante, porém, ter que ligar isAdded()antes de cada acesso ... torna o código feio.
Ixx 28/02
25
Não parece ser muito de uma diferença entre ter if(isAdded())ouif(getActivity() != null)
StackOverflowed
19

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, adicionei um ouvinte. Sempre que precisar, em Fragments Activityvez do padrão getActivity(), preciso ligar

 getAvailableActivity(new IActivityEnabledListener() {
        @Override
        public void onActivityEnabled(FragmentActivity activity) {
            // Do manipulations with your activity
        }
    });
Paul Freez
fonte
Ótima resposta! deve ser marcado como o correto, pois resolve o problema real: no meu caso, não é suficiente verificar se getActivity () não é nulo, pois preciso concluir minha tarefa, não importa o quê. Estou usando isso e funciona perfeitamente.
Hadas Kaminsky
18

O melhor para se livrar disso é manter a referência da atividade quando onAttachfor chamada e usar a referência da 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, pois onAttach(Activity)está depreciado e agora onAttach(Context)está sendo usado

Pawan Maheshwari
fonte
9
Fragments sempre mantém a referência da atividade pai e o disponibiliza com o método getActivity (), aqui mantemos a mesma referência.
Pawan Maheshwari
8
O Google realmente recomenda isso se você precisar do seu fragmento para compartilhar eventos com a atividade. developer.android.com/guide/components/fragments.html (procure "Criando callbacks de evento para a atividade")
Vering
6
convém adicionar o método onDetach, que anula a referência da atividade
meia
2
sim, inicialize mActivity = null no método onDetach para anular essa referência de atividade.
Pawan Maheshwari
19
nunca faça isso. você está vazando sua atividade completa (e com ela toda a árvore de layout, com desenháveis ​​e afins). Se getActivity()retornar nulo, é porque você não está mais em uma atividade. Esta é uma solução alternativa suja.
Njzk2
10

Não chame métodos dentro do Fragmento que exijam getActivity () até onStart na Atividade pai.

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();
}
bvmobileapps
fonte
Na verdade, tive um problema semelhante porque estava iniciando a tarefa no construtor de fragmentos. Muito obrigado.
Supreme Dolphin
4

eu estive lutando contra esse tipo de problema há um tempo e acho que encontrei uma solução confiável.

É muito difícil ter certeza de que isso this.getActivity()não retornará nullpara um Fragment, especialmente se você estiver lidando com algum tipo de comportamento de rede que dê tempo suficiente ao seu código para se retirarActivity referências.

Na solução abaixo, declaro uma pequena classe de gerenciamento chamada ActivityBuffer. Essencialmente, isso classlida com a manutenção de uma referência confiável a uma propriedade Activitye com a promessa de executar Runnables dentro de um Activitycontexto válido sempre que houver uma referência válida disponível. Eles Runnableestão agendados para execução no thread da interface do usuário imediatamente, se Contextdisponível, caso contrário, a execução será adiada até que Contextesteja 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 todas as áreas dentro da sua Fragmentextensão BaseFragmentque você não é confiável em relação a uma ligação getActivity(), basta ligar this.getActivityBuffer().safely(...)e declarar uma ActivityBuffer.IRunnablepara a tarefa!

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

O ActivityBufferpode então ser usado da seguinte maneira:

this.getActivityBuffer().safely(
  new ActivityBuffer.IRunnable() {
    @Override public final void run(final Activity pActivity) {
       // Do something with guaranteed Context.
    }
  }
);
Mapsy
fonte
Você pode adicionar um exemplo do uso do método this.getActivityBuffer (). Safety (...).
Fahad_sust 12/04/19
3
@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    // run the code making use of getActivity() from here
}
Mohanraj Balasubramaniam
fonte
Você poderia elaborar mais sua resposta adicionando um pouco mais de descrição sobre a solução que você fornece?
abarisone
1

Sei que essa é uma pergunta antiga, mas acho que devo fornecer minha resposta, porque meu problema não foi resolvido por outras pessoas.

primeiro de tudo: eu estava adicionando fragmentos dinamicamente usando fragmentTransactions. Segundo: meus fragmentos foram modificados usando AsyncTasks (consultas de banco de dados em um servidor). Terceiro: meu fragmento não foi instanciado no início da atividade. Quarto: usei uma instanciação personalizada de fragmento "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 pouco antes. Não sei por que, provavelmente por causa do "commit" ser feito posteriormente, o fragmento ainda não foi adicionado na hora de removê-lo. Portanto, getActivity () estava retornando nulo.

Solução: 1) Eu tive que verificar se estava tentando encontrar corretamente a primeira instância do fragmento antes de criar uma nova. 2) Eu tive que colocar serRetainInstance (true) nesse fragmento para mantê-lo na mudança de orientação (sem backstack). necessário, portanto, não há problema) 3) Em vez de "recriar ou obter fragmento antigo" imediatamente antes de "removê-lo", coloquei o fragmento diretamente 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ê-lo, evitou problemas de getActivity.

Feuby
fonte
0

No Kotlin, você pode tentar desta maneira lidar com a condição nula getActivity ().

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

        //your code here

   }

Ele verificará se a atividade é nula ou não e, se não for nula, execute o código interno.

Sachin
fonte