6

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ó.

+0

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

+0

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

+0

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

Trả lời

3

Tôi có một ứng dụng sử dụng thanh công cụ (mã dựa trên com.example.android.actionbarcompat), nơi tôi gặp sự cố tương tự.Trên một số thiết bị, onCreateOptionsMenu() không được gọi ngay sau khi onCreate() có nghĩa là thanh tác vụ không được khởi tạo và khi tôi thử thay đổi một biểu tượng, ứng dụng đã bị lỗi.

Tin tốt cho tôi là onCreateOptionsMenu() không được gọi một chút sau đó và khi nó xảy ra, tôi giữ một tham chiếu đến menu trong onCreateOptionsMenu(). Tôi đã sửa đổi phần còn lại của mã hoạt động của mình để kiểm tra tham chiếu này trước khi họ làm bất kỳ điều gì với thanh tác vụ. Điều này vẫn thực hiện siêu nhanh và trải nghiệm người dùng không bị ảnh hưởng.

Tôi đề nghị bạn làm như vậy và hoãn một số khởi tạo.

+0

Ok, trì hoãn việc khởi chạy đánh tôi như một ý tưởng hay. Tôi chắc chắn sẽ cung cấp cho nó một thử và xem nếu đó sửa chữa vấn đề. – bjg222

0

Tôi gặp sự cố tương tự với ứng dụng có Fragments trong tab và NPE khi thay đổi xoay. Lý do là tài liệu tham khảo Activity trong Fragments đôi khi không có giá trị. Tôi tránh những vấn đề NPE bằng cách thay đổi các tham chiếu Activity để tham khảo bối cảnh ứng dụng mà tôi có thể và bằng cách kiểm tra tất cả các tài liệu tham khảo Hoạt động còn lại trong Những mảnh vỡ for null:

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