6

Ho un'app che utilizza schede nella barra delle azioni in ICS, dove ogni scheda contiene un frammento al suo interno. In determinate circostanze, dopo aver premuto un pulsante nel menu delle opzioni sulla barra delle azioni, quindi ruotare il dispositivo, ottengo una NullPointerException. Posso riprodurlo in modo affidabile con lo stesso insieme di passaggi, ma ci sono alcuni casi (come se non premo alcun pulsante sulla barra delle azioni) non produrre l'eccezione. L'eccezione non sembra fare riferimento a nessuna riga nel mio codice e si verifica durante la ricreazione dell'attività dopo un cambiamento di orientamento.NullPointerException sulla rotazione durante dispatchCreateOptionsMenu, stack trace non include alcuna funzione nella mia app

Ecco l'eccezione:

09-18 20:56:22.357: E/AndroidRuntime(689): FATAL EXCEPTION: main 
09-18 20:56:22.357: E/AndroidRuntime(689): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.andavapps.flightbot/com.andavapps.flightbot.FlightBotActivity}: java.lang.NullPointerException 
09-18 20:56:22.357: E/AndroidRuntime(689):  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1956) 
09-18 20:56:22.357: E/AndroidRuntime(689):  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1981) 
09-18 20:56:22.357: E/AndroidRuntime(689):  at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3351) 
09-18 20:56:22.357: E/AndroidRuntime(689):  at android.app.ActivityThread.access$700(ActivityThread.java:123) 
09-18 20:56:22.357: E/AndroidRuntime(689):  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1151) 
09-18 20:56:22.357: E/AndroidRuntime(689):  at android.os.Handler.dispatchMessage(Handler.java:99) 
09-18 20:56:22.357: E/AndroidRuntime(689):  at android.os.Looper.loop(Looper.java:137) 
09-18 20:56:22.357: E/AndroidRuntime(689):  at android.app.ActivityThread.main(ActivityThread.java:4424) 
09-18 20:56:22.357: E/AndroidRuntime(689):  at java.lang.reflect.Method.invokeNative(Native Method) 
09-18 20:56:22.357: E/AndroidRuntime(689):  at java.lang.reflect.Method.invoke(Method.java:511) 
09-18 20:56:22.357: E/AndroidRuntime(689):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784) 
09-18 20:56:22.357: E/AndroidRuntime(689):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551) 
09-18 20:56:22.357: E/AndroidRuntime(689):  at dalvik.system.NativeStart.main(Native Method) 
09-18 20:56:22.357: E/AndroidRuntime(689): Caused by: java.lang.NullPointerException 
09-18 20:56:22.357: E/AndroidRuntime(689):  at android.app.FragmentManagerImpl.dispatchCreateOptionsMenu(FragmentManager.java:1831) 
09-18 20:56:22.357: E/AndroidRuntime(689):  at android.app.Activity.onCreatePanelMenu(Activity.java:2445) 
09-18 20:56:22.357: E/AndroidRuntime(689):  at com.android.internal.policy.impl.PhoneWindow.preparePanel(PhoneWindow.java:388) 
09-18 20:56:22.357: E/AndroidRuntime(689):  at com.android.internal.policy.impl.PhoneWindow.invalidatePanelMenu(PhoneWindow.java:739) 
09-18 20:56:22.357: E/AndroidRuntime(689):  at com.android.internal.policy.impl.PhoneWindow.restorePanelState(PhoneWindow.java:1664) 
09-18 20:56:22.357: E/AndroidRuntime(689):  at com.android.internal.policy.impl.PhoneWindow.restoreHierarchyState(PhoneWindow.java:1619) 
09-18 20:56:22.357: E/AndroidRuntime(689):  at android.app.Activity.onRestoreInstanceState(Activity.java:906) 
09-18 20:56:22.357: E/AndroidRuntime(689):  at android.app.Activity.performRestoreInstanceState(Activity.java:878) 
09-18 20:56:22.357: E/AndroidRuntime(689):  at android.app.Instrumentation.callActivityOnRestoreInstanceState(Instrumentation.java:1100) 
09-18 20:56:22.357: E/AndroidRuntime(689):  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1934) 
09-18 20:56:22.357: E/AndroidRuntime(689):  ... 12 more 

Ed ecco il mio codice di attività (rimosso un certo codice non correlato per semplicità di mostrare qui)

public class MyActivity extends Activity { 

    private class MyTabListener<C extends MyFragment> implements ActionBar.TabListener { 

     private Activity activity; 
     private MyFragment fragmentMain; 
     private MyFragment fragmentSide; 
     private Class<C> cls; 

     public MyTabListener(Activity a, Class<C> c) { 
      activity = a; 
      cls = c; 
     } 

     @Override 
     public void onTabReselected(Tab tab, FragmentTransaction ft) { } 

     @Override 
     public void onTabSelected(Tab tab, FragmentTransaction ft) { 
      if (sidebar) { 
       if (fragmentSide == null) { 
        fragmentSide = (MyFragment) Fragment.instantiate(activity, cls.getName()); 
        ft.add(c.SIDE_FRAME, fragmentSide, fragmentSide.getViewTag()); 
       } else 
        ft.attach(fragmentSide); 
      } else { 
       if (fragmentMain == null) { 
        fragmentMain = (MyFragment) Fragment.instantiate(activity, cls.getName()); 
        ft.add(c.MAIN_FRAME, fragmentMain, fragmentMain.getViewTag()); 
       } else 
        ft.attach(fragmentMain); 
      } 
      selected = tabs.indexOf(tab); 
      mainUp = false; 
      if (setUpComplete) 
       invalidateOptionsMenu(); 
     } 

     @Override 
     public void onTabUnselected(Tab tab, FragmentTransaction ft) { 
      if (fragmentSide != null && !fragmentSide.isDetached()) 
       ft.detach(fragmentSide); 
      if (fragmentMain != null && !fragmentMain.isDetached()) 
       ft.detach(fragmentMain); 
     } 

    } 

    private class MyMainTabListener<C extends MyFragment> implements ActionBar.TabListener { 

     private Activity activity; 
     private MyFragment fragmentMain; 
     private Class<C> cls; 

     public MyMainTabListener(Activity a, Class<C> c) { 
      activity = a; 
      cls = c; 
     } 

     @Override 
     public void onTabReselected(Tab tab, FragmentTransaction ft) { } 

     @Override 
     public void onTabSelected(Tab tab, FragmentTransaction ft) { 
      if (fragmentMain == null) { 
       fragmentMain = (MyFragment) Fragment.instantiate(activity, cls.getName()); 
       ft.add(c.MAIN_FRAME, fragmentMain, fragmentMain.getViewTag()); 
      } else if (fragmentMain.isDetached()) 
       ft.attach(fragmentMain); 
      mainUp = !sidebar; 
      if (setUpComplete) 
       invalidateOptionsMenu(); 
     } 

     @Override 
     public void onTabUnselected(Tab tab, FragmentTransaction ft) { 
      if (!sidebar && fragmentMain != null && !fragmentMain.isDetached()) 
       ft.detach(fragmentMain); 
      else if (sidebar && (fragmentMain == null || fragmentMain.isDetached())) { 
       if (fragmentMain == null) { 
        fragmentMain = (MyFragment) Fragment.instantiate(activity, cls.getName()); 
        ft.add(c.MAIN_FRAME, fragmentMain, fragmentMain.getViewTag()); 
       } else if (fragmentMain.isDetached()) 
        ft.attach(fragmentMain); 
      } 
     } 

    } 

    private static final class c { 
     //A bunch of constants are defined here 
    } 

    private ArrayList<ActionBar.Tab> tabs = new ArrayList<ActionBar.Tab>(); 

    private int side; 
    private int orient; 
    private int selected; 
    private boolean sidebar; 
    private boolean mainUp; 
    private boolean lock; 
    private boolean setUpComplete = false; 

    @Override 
    public void onCreate(Bundle inState) { 
     super.onCreate(inState); 
     setContentView(R.layout.main_rel); 

     orient = detectOrientation(); 

     if (inState != null) { 
      selected = inState.getInt("selected", 1); 
      side = inState.getInt("side", c.LEFT_SIDE); 
      sidebar = inState.getBoolean("visible", true); 
      mainUp = inState.getBoolean("mainup", !sidebar); 
      lock = inState.getBoolean("lock", false); 
     } else { 
      selected = 1; 
      side = c.LEFT_SIDE; 
      sidebar = true; 
      mainUp = false; 
      lock = false; 
     } 

     ActionBar ab = getActionBar(); 
     ab.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); 

     tabs.add(ab.newTab().setText(MainFragment.getTabText()).setTabListener(new MyMainTabListener<MainFragment>(this, MainFragment.class))); 
     tabs.add(ab.newTab().setText(OtherFragment.getTabText()).setTabListener(new MyTabListener<OtherFragment>(this, OtherFragment.class))); 
     tabs.add(ab.newTab().setText(AnotherFragment.getTabText()).setTabListener(new MyTabListener<AnotherFragment>(this, AnotherFragment.class))); 
     tabs.add(ab.newTab().setText(YetAnotherFragment.getTabText()).setTabListener(new MyTabListener<YetAnotherFragment>(this, YetAnotherFragment.class))); 
    } 

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

     if (!setUpComplete) 
      setUp(); 
    } 

    @Override 
    protected void onResume() { 
     super.onResume(); 

     if (!setUpComplete) 
      setUp(); 
    } 

    @Override 
    protected void onPause() { 
     super.onPause(); 
    } 

    @Override 
    protected void onStop() { 
     super.onStop(); 
    } 

    @Override 
    protected void onDestroy() { 
     super.onDestroy(); 
    } 

    @Override 
    public void onSaveInstanceState(Bundle outState) { 
     setUpComplete = false; 
     getActionBar().removeAllTabs(); 

     super.onSaveInstanceState(outState); 
     outState.putInt("selected", selected); 
     outState.putInt("side", side); 
     outState.putBoolean("visible", sidebar); 
     outState.putBoolean("mainup", mainUp); 
    } 

    @Override 
    public boolean onCreateOptionsMenu(Menu menu) { 
     getMenuInflater().inflate(R.menu.menu, menu); 
     return true; 
    } 

    @Override 
    public boolean onPrepareOptionsMenu(Menu menu) { 
     menu.findItem(R.id.menu_showhide).setTitle(c.MENU_SHOW_TEXT[(sidebar ? 1 : 0)]).setIcon(c.MENU_SHOW_DRAW[(sidebar ? 1 : 0)][side][orient][(mainUp ? 0 : 1)]); 
     menu.findItem(R.id.menu_swapside).setTitle(c.MENU_SIDE_TEXT[side][orient]).setIcon(c.MENU_SIDE_DRAW[side][orient]); 
     menu.findItem(R.id.menu_lock).setTitle(c.MENU_LOCK_TEXT[(lock ? 1 : 0)]).setIcon(c.MENU_LOCK_DRAW[(lock ? 0 : 1)]); 
     if (!sidebar) 
      menu.findItem(R.id.menu_swapside).setVisible(false).setEnabled(false); 
     return true; 
    } 

    @Override 
    public boolean onOptionsItemSelected(MenuItem item) { 
     switch (item.getItemId()) { 
      case R.id.menu_swapside: 
       toggleSide(); 
       return true; 
      case R.id.menu_showhide: 
       toggleVisibility(); 
       return true; 
      case R.id.menu_lock: 
       toggleRotation(); 
       return true; 
      default: 
       return super.onOptionsItemSelected(item); 
     } 
    } 

    private int detectOrientation() { 
      int o = getResources().getConfiguration().orientation; 
      int r = getWindowManager().getDefaultDisplay().getRotation(); 

      if (o == Configuration.ORIENTATION_LANDSCAPE && (r == Surface.ROTATION_0 || r == Surface.ROTATION_90)) 
       return c.LAND_ORIENT; 
      else if (o == Configuration.ORIENTATION_PORTRAIT && (r == Surface.ROTATION_90 || r == Surface.ROTATION_180)) 
       return c.RPORT_ORIENT; 
      else if (o == Configuration.ORIENTATION_LANDSCAPE && (r == Surface.ROTATION_180 || r == Surface.ROTATION_270)) 
       return c.RLAND_ORIENT; 
      else 
       return c.PORT_ORIENT; 
    } 

    private void toggleVisibility() { 
     sidebar = !sidebar; 
     mainUp = !sidebar; 
     invalidateOptionsMenu(); 
     setUpTabs(); 
    } 

    private void toggleSide() { 
     side = (side == c.RIGHT_SIDE ? c.LEFT_SIDE : c.RIGHT_SIDE); 
     invalidateOptionsMenu(); 
     setUpSide(); 
    } 

    private void toggleRotation() { 
     lock = !lock; 
     invalidateOptionsMenu(); 
     setUpLock(); 
    } 

    private void setUp() { 
     setUpTabs(); 
     setUpSide(); 
     setUpLock(); 
     setUpComplete = true; 
    } 

    private void setUpTabs() { 
     ActionBar ab = getActionBar(); 
     ab.removeAllTabs(); 

     ab.addTab(tabs.get(0), sidebar || mainUp); 
     if (sidebar) 
      ab.removeTab(tabs.get(0)); 
     for (int i = 1; i < tabs.size(); i ++) 
      ab.addTab(tabs.get(i), !mainUp && selected == i); 
    } 

    private void setUpSide() { 
     RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT); 
     params.addRule(c.SIDE_RULE[side][orient], c.SIDE_FRAME); 
     FrameLayout mf = (FrameLayout) findViewById(c.MAIN_FRAME); 
     mf.setLayoutParams(params); 

     params = new RelativeLayout.LayoutParams(c.LAYOUT_WIDTH[orient], c.LAYOUT_HEIGHT[orient]); 
     params.addRule(c.ALIGN_RULE[side][orient]); 
     FrameLayout sf = (FrameLayout) findViewById(c.SIDE_FRAME); 
     sf.setLayoutParams(params); 
    } 

    private void setUpLock() { 
     setRequestedOrientation((lock ? c.LOCK_ORIENT[orient] : ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)); 
    } 

} 

Un paio di note circa la mia app e il codice per spiegare cose:

  • L'app visualizza un frammento principale e un frammento di barra laterale
  • Il menu delle opzioni contiene tre pulsanti: uno per passare la barra laterale da un lato all'altro dello schermo, uno per nascondere la barra laterale e l'altro per bloccare l'orientamento
  • Il frammento principale è sempre il primo nell'elenco di schede ed è sempre di tipo MainFragment
  • Sto eseguendo questo su due dispositivi che eseguono ICS (Asus Trans Prime, 4.0.4; HTC Vivid, 4.0.3) & l'emulatore (ICS 4.0.3 & JB 4.1). Questo succede solo su ICS.

L'eccezione si verifica con la seguente sequenza:

  • lancio app pulsante
  • premere per nascondere barra laterale
  • dispositivo ruoti

Se qualcos'altro accade prima di ruotare il dispositivo, l'eccezione non si verifica. Ad esempio, se la barra laterale è nascosta, non ottengo l'eccezione. Se il dispositivo viene ruotato per primo, l'eccezione non si verificherà mai, quindi anche se la barra laterale è nascosta e il dispositivo viene ruotato di nuovo, non ottengo l'eccezione. E la traccia dello stack non fa riferimento a una singola funzione nel mio codice, quindi posso persino sembrare che trovi la causa principale.

Sembra questa è la funzione in FragmentManager.java (pacchetto android.app) che genera l'eccezione:

1827  public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) { 
1828   boolean show = false; 
1829   ArrayList<Fragment> newMenus = null; 
1830   if (mActive != null) { 
1831    for (int i=0; i<mAdded.size(); i++) { 
1832     Fragment f = mAdded.get(i); 
1833     if (f != null && !f.mHidden && f.mHasMenu && f.mMenuVisible) { 
1834      show = true; 
1835      f.onCreateOptionsMenu(menu, inflater); 
1836      if (newMenus == null) { 
1837       newMenus = new ArrayList<Fragment>(); 
1838      } 
1839      newMenus.add(f); 
1840     } 
1841    } 
1842   } 
1843   
1844   if (mCreatedMenus != null) { 
1845    for (int i=0; i<mCreatedMenus.size(); i++) { 
1846     Fragment f = mCreatedMenus.get(i); 
1847     if (newMenus == null || !newMenus.contains(f)) { 
1848      f.onDestroyOptionsMenu(); 
1849     } 
1850    } 
1851   } 
1852   
1853   mCreatedMenus = newMenus; 
1854   
1855   return show; 
1856  } 

Non c'è alcun controllo su nulla mAdded prima di tentare di usarlo. La stessa funzione in JB sostituisce (mActive != null) con (mAdded != null). Ma non ho idea di cosa potrei fare per ICS come soluzione alternativa per evitarlo.

Qualcuno ha qualche idea? Ho setacciato StackOverflow alla ricerca di problemi simili, ma finora sono uscito vuoto. Grazie! Se c'è qualcos'altro che devo pubblicare, fammelo sapere e lo aggiungerò.

+0

si può tenere intatto l'eccezione i numeri di riga. stai usando l'emulatore jellybean per eseguire questo? – nandeesh

+0

Ho passato un bel po 'di tempo per il debug e non posso rintracciarlo a una fonte, motivo per cui sto passando a StackOverflow. Non ci sono numeri di riga nell'eccezione, poiché si tratta di tutte le funzioni di sistema. I numeri di riga sarebbero fantastici, perché potrei andare alla fonte Android e individuare la causa esatta. Così com'è, ho passato attraverso la maggior parte delle funzioni in quella traccia stack nella sorgente Android e non riesco a trovare molto. Lo sto eseguendo su due dispositivi fisici diversi (ottenere la stessa cosa da entrambi) - un Asus Transformer Prime con 4.0.4 e un HTC Vivid con 4.0.3 – bjg222

+0

Ok, ho avuto la possibilità di eseguirlo sull'emulatore piuttosto che un dispositivo fisico e la traccia dello stack ora include i numeri di riga per le funzioni native di Android. Ho anche aggiornato la domanda con ulteriori dettagli. È venuto fuori che non succede in JB, solo ICS. (Non ho provato prima, dato che la mia app è indirizzata a 4.0 e sopra) – bjg222

risposta

3

Ho un'app che utilizza una barra degli strumenti (codice basato su com.example.android.actionbarcompat) in cui ho riscontrato lo stesso problema.Su alcuni dispositivi, onCreateOptionsMenu() non viene chiamato subito dopo onCreate(), il che significa che la barra delle azioni non è stata inizializzata e quando ho provato a cambiare un'icona, l'app si è bloccata.

La buona notizia per me è che onCreateOptionsMenu() viene chiamato un po 'più tardi e quando succede, tengo un riferimento al menu in onCreateOptionsMenu(). Ho modificato il resto del mio codice attività per verificare questo riferimento prima che facciano qualcosa con la barra delle azioni. Ciò si esegue ancora in modo super veloce e l'esperienza dell'utente non viene influenzata.

Suggerisco di fare lo stesso e posticipare l'inizializzazione.

+0

Ok, differire l'inizializzazione mi sembra comunque una buona idea. Farò sicuramente una prova e vedere se questo risolve il problema. – bjg222

0

Ho riscontrato un problema simile con un'app che aveva Fragments in schede e NPE su cambio di rotazione. Il motivo era che i riferimenti Activity in Fragments erano talvolta nulli. Evito i problemi NPE cambiando le Activity riferimenti al contesto riferimenti app dove ho potuto e testando tutti i riferimenti di attività rimanenti in Fragments per nulla:

if(getActivity() != null) { 
    getActivity.someMethod() 
}; 
Problemi correlati