Tôi có một ứng dụng sử dụng các tab trên thanh hành động trong ICS, trong đó mỗi tab có một đoạn bên trong nó. Trong những trường hợp nhất định, sau khi tôi đã đẩy một nút trên menu tùy chọn trên thanh hành động, sau đó xoay thiết bị, tôi nhận được một NullPointerException. Tôi có thể tái tạo nó một cách đáng tin cậy với cùng một loạt các bước, nhưng có một số trường hợp (như nếu tôi không nhấn bất kỳ nút nào trên thanh tác vụ) không tạo ra ngoại lệ. Ngoại lệ dường như không tham chiếu đến bất kỳ dòng nào trong mã của tôi và xảy ra trong quá trình giải trí hoạt động sau khi thay đổi định hướng.NullPointerException khi xoay trong quá trình dispatchCreateOptionsMenu, ngăn xếp ngăn xếp không bao gồm bất kỳ chức năng nào trong ứng dụng của tôi
Đây là ngoại lệ:
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
Và đây là mã hoạt động của tôi (loại bỏ một số mã không liên quan vì đơn giản hiển thị nó ở đây)
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));
}
}
Một vài ghi chú về ứng dụng của tôi và mã số để giải thích điều:
- Ứng dụng hiển thị đoạn chính và đoạn thanh bên
- Trình đơn tùy chọn chứa ba nút: Một để chuyển thanh bên từ một phía của màn hình sang một bên, để ẩn thanh bên và một để khóa hướng
- Đoạn chính luôn là đầu tiên trong danh sách tab và luôn là loại MainFragment
- Tôi đang chạy trên hai thiết bị đang chạy ICS (Asus Trans Prime, 4.0.4; HTC Vivid, 4.0.3) & Trình mô phỏng (ICS 4.0.3 & JB 4.1). Điều này chỉ xảy ra trên ICS.
Trường hợp ngoại lệ xảy ra với trình tự sau đây:
- Khởi chạy ứng dụng nút
- báo chí để ẩn sidebar
- xoay thiết bị
Nếu bất cứ điều gì khác xảy ra trước khi xoay thiết bị, ngoại lệ không xảy ra. Ví dụ: nếu thanh bên bị bỏ ẩn, tôi không nhận được ngoại lệ. Nếu thiết bị được xoay đầu tiên, ngoại lệ sẽ không bao giờ xảy ra, vì vậy ngay cả khi thanh bên bị ẩn và thiết bị được xoay một lần nữa, tôi không nhận được ngoại lệ. Và dấu vết ngăn xếp không tham chiếu đến một hàm duy nhất trong mã của tôi, vì vậy tôi thậm chí có thể định vị nguyên nhân gốc rễ.
Có vẻ như đây là các chức năng trong FragmentManager.java (gói android.app) mà ném ngoại lệ:
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 }
Không có kiểm tra null trên mAdded
trước khi cố gắng sử dụng nó. Hàm tương tự trong JB thay thế (mActive != null)
bằng (mAdded != null)
. Nhưng tôi không biết tôi có thể làm gì cho ICS như một giải pháp để tránh điều này.
Bất kỳ ai có ý tưởng nào? Tôi đã lùng sục StackOverflow tìm kiếm các vấn đề tương tự, nhưng lại có sản phẩm nào. Cảm ơn! Nếu có bất kỳ điều gì khác tôi cần đăng, hãy cho tôi biết và tôi sẽ thêm nó.
bạn có thể giữ số dòng còn nguyên vẹn trong ngoại lệ. bạn đang sử dụng giả lập jellybean để chạy cái này? – nandeesh
Tôi đã dành khá nhiều thời gian gỡ lỗi, và không thể theo dõi nó vào một nguồn, đó là lý do tại sao tôi chuyển sang StackOverflow. Không có số dòng trong ngoại lệ, vì đó là tất cả các chức năng của hệ thống. Số dòng sẽ là tuyệt vời, bởi vì sau đó tôi có thể đi đến nguồn Android và xác định nguyên nhân chính xác. Vì nó là, tôi đã đi qua hầu hết các chức năng trong dấu vết ngăn xếp trong nguồn Android và không thể tìm thấy nhiều. Tôi đang chạy nó trên hai thiết bị vật lý khác nhau (có cùng một thứ từ cả hai) - một Asus Transformer Prime chạy 4.0.4 và HTC Vivid chạy 4.0.3 – bjg222
Ok, tôi đã có cơ hội chạy nó trên trình mô phỏng thay vì một thiết bị vật lý và theo dõi ngăn xếp giờ đây bao gồm số dòng cho các chức năng Android gốc. Tôi cũng đã cập nhật câu hỏi với một số chi tiết khác. Hóa ra nó không xảy ra trong JB, chỉ cần ICS. (Chưa thử trước đó, vì ứng dụng của tôi được nhắm mục tiêu ở 4.0 trở lên) – bjg222