Dans les rapports d'erreur de la console du développeur, je vois parfois des rapports avec un problème NPE. Je ne comprends pas ce qui ne va pas avec mon code. L'application émulateur et mon appareil fonctionne bien sans forcecloses. Cependant, certains utilisateurs obtiennent NullPointerException dans la classe fragment lorsque la méthode getActivity () est appelée.
Activité
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 fragment
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();
...
}
}
Peut-être que cette erreur se produit lorsque les applications ont repris l’arrière-plan. Dans ce cas, comment dois-je gérer cette situation correctement?
Il semble que j'ai trouvé une solution à mon problème. De très bonnes explications sont données ici et ici . Voici mon exemple:
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) {}
});
L'idée principale de ce code est que, tout en exécutant votre application normalement, vous créez de nouveaux fragments et les transmettez à l'adaptateur. Lorsque vous reprenez votre gestionnaire de fragments d'application a déjà l'instance de ce fragment et vous devez l'obtenir à partir du gestionnaire de fragments et le transmettre à l'adaptateur.
UPDATE
En outre, il est recommandé d'utiliser des fragments pour vérifier isAdded avant d'appeler getActivity (). Cela permet d'éviter une exception de pointeur nul lorsque le fragment est détaché de l'activité. Par exemple, une activité peut contenir un fragment qui pousse une tâche asynchrone. Lorsque la tâche est terminée, le programme d'écoute onTaskComplete est appelé.
@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();
}
}
Si nous ouvrons le fragment, appuyez sur une tâche, puis appuyez rapidement sur pour revenir à une activité précédente. Une fois la tâche terminée, il tentera d'accéder à l'activité dans onPostExecute () en appelant la méthode getActivity (). Si l'activité est déjà détachée et que cette vérification est absente:
if (isAdded())
puis l'application se bloque.
Ok, je sais que cette question est en fait résolue mais j'ai décidé de partager ma solution pour cela. J'ai créé une classe parent abstraite pour mon 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;
}
}
}
Comme vous pouvez le voir, j'ai ajouté un auditeur, donc, chaque fois que je devrai obtenir Fragments
Activity
au lieu du standard getActivity()
, j'aurai besoin d'appeler
getAvailableActivity(new IActivityEnabledListener() {
@Override
public void onActivityEnabled(FragmentActivity activity) {
// Do manipulations with your activity
}
});
Le mieux est de conserver cette référence lorsque onAttach
est appelée et de l’utiliser chaque fois que nécessaire, par exemple.
@Override
public void onAttach(Context context) {
super.onAttach(context);
mContext = context;
}
@Override
public void onDetach() {
super.onDetach();
mContext = null;
}
Edité, puisque onAttach(Activity)
est déprécié et maintenant onAttach(Context)
est utilisé
N'appelez pas de méthodes dans le fragment qui requièrent getActivity () jusqu'à onStart dans l'activité parent.
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();
}
Je suis aux prises avec ce genre de problème depuis un certain temps et je pense avoir mis au point une solution fiable.
Il est assez difficile de savoir avec certitude que this.getActivity()
ne retournera pas null
pour un Fragment
, en particulier si vous avez affaire à un type de comportement réseau qui donne suffisamment de temps à votre code. retirer Activity
références.
Dans la solution ci-dessous, je déclare une petite classe de gestion appelée ActivityBuffer
. Cette class
concerne essentiellement le maintien d'une référence fiable à un Activity
propriétaire et la promesse d'exécuter Runnable
s dans un contexte Activity
valide chaque fois qu'une référence valide est disponible. Les Runnable
s sont programmés pour une exécution sur le thread d'interface utilisateur immédiatement si le Context
est disponible, sinon l'exécution est différée jusqu'à ce que Context
soit prêt.
/** 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;
}
}
En termes de mise en œuvre, nous devons veiller à appliquer les méthodes du cycle de vie afin qu'elles coïncident avec le comportement décrit ci-dessus par 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;
}
}
Enfin, dans toutes les zones de votre Fragment
qui étend BaseFragment
et que vous n'êtes pas digne de confiance vis-à-vis d'un appel à getActivity()
, appelez simplement this.getActivityBuffer().safely(...)
et déclarez un ActivityBuffer.IRunnable
pour la tâche!
Le contenu de votre void run(final Activity pActivity)
est alors garanti pour être exécuté le long du thread UI.
La ActivityBuffer
peut alors être utilisée comme suit:
this.getActivityBuffer().safely(
new ActivityBuffer.IRunnable() {
@Override public final void run(final Activity pActivity) {
// Do something with guaranteed Context.
}
}
);
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// run the code making use of getActivity() from here
}
Je sais que c’est une vieille question, mais je pense que je dois y répondre car mon problème n’a pas été résolu par d’autres.
tout d’abord: j’ajoutais dynamiquement des fragments à l’aide de fragmentTransactions. Deuxièmement: mes fragments ont été modifiés en utilisant AsyncTasks (requêtes DB sur un serveur). Troisièmement: mon fragment n’était pas instancié au début de l’activité. Quatrièmement: j’ai utilisé une instanciation de fragment personnalisée "créez ou chargez-le" pour obtenir la variable fragment. Quatrième: l'activité a été recréée à cause du changement d'orientation
Le problème était que je voulais "supprimer" le fragment à cause de la réponse à la requête, mais le fragment avait été créé de manière incorrecte juste avant. Je ne sais pas pourquoi, probablement à cause du "commit" être fait plus tard, le fragment n'a pas encore été ajouté quand il était temps de le supprimer. Par conséquent, getActivity () renvoyait la valeur null.
Solution: 1) Je devais vérifier que j'essayais correctement de trouver la première instance du fragment avant d'en créer un nouveau. 2) Je devais mettre serRetainInstance (true) sur ce fragment afin de le conserver malgré le changement d'orientation (pas de backstack). donc pas de problème) 3) Au lieu de "recréer ou récupérer un vieux fragment" juste avant de "le retirer", je mets directement le fragment au début de l'activité. L'instancier au début de l'activité au lieu de "charger" (ou instancier) la variable fragment avant de la supprimer empêchait les problèmes de getActivity.