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ò.
si può tenere intatto l'eccezione i numeri di riga. stai usando l'emulatore jellybean per eseguire questo? – nandeesh
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
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