6

ICS のアクション バーにタブを使用するアプリがあります。各タブにはフラグメントが含まれています。特定の状況下で、アクション バーのオプション メニューのボタンを押してデバイスを回転させた後、NullPointerException が発生します。同じ一連の手順で確実に再現できますが、例外が発生しない場合があります (アクション バーのボタンを押さない場合など)。例外はコード内のどの行も参照していないようで、向きの変更後のアクティビティの再作成中に発生します。

例外は次のとおりです。

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

そして、これが私のアクティビティコードです(ここで簡単に表示できるように、無関係なコードをいくつか削除しました)

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));
    }

}

私のアプリと物事を説明するためのコードに関するいくつかのメモ:

  • アプリはメイン フラグメントとサイドバー フラグメントを表示します
  • オプション メニューには 3 つのボタンがあります。1 つはサイドバーを画面の片側からもう一方の側に切り替えるボタン、もう 1 つはサイドバーを非表示にするボタン、もう 1 つは方向をロックするボタンです。
  • メイン フラグメントは常にタブ リストの最初にあり、常に MainFragment 型です。
  • ICS(Asus Trans Prime、4.0.4; HTC Vivid、4.0.3)とエミュレータ(ICS 4.0.3およびJB 4.1)を実行している2つのデバイスでこれを実行しています。これは ICS でのみ発生します。

例外は、次のシーケンスで発生します。

  • アプリを起動
  • ボタンを押すとサイドバーが非表示になります
  • デバイスを回転させる

デバイスをローテーションする前に何か他のことが起こった場合、例外は発生しません。たとえば、サイドバーが非表示になっている場合、例外は発生しません。デバイスを最初に回転させた場合、例外は発生しないため、サイドバーを非表示にしてデバイスを再度回転させても、例外は発生しません。また、スタック トレースはコード内の関数を 1 つも参照していないため、根本的な原因を突き止めているようにも見えます。

これは例外をスローする FragmentManager.java (パッケージ android.app) の関数のようです:

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     }

mAdded使用する前にnull チェックはありません。JB の同じ機能が に置き換え(mActive != null)られ(mAdded != null)ます。しかし、これを回避するための回避策として ICS に対して何をすればよいかわかりません。

誰にもアイデアはありますか?同様の問題を探して StackOverflow を精査しましたが、これまでのところ空っぽです。ありがとう!他に投稿する必要がある場合は、お知らせください。追加します。

4

3 に答える 3

3

同じ問題に遭遇したツールバー (com.example.android.actionbarcompat に基づくコード) を使用するアプリがあります。一部のデバイスでは、onCreate() の直後に onCreateOptionsMenu() が呼び出されないため、アクション バーが初期化されず、アイコンを変更しようとするとアプリがクラッシュしました。

私にとっての朗報は、onCreateOptionsMenu() が呼び出されるのが少し後であり、それが発生したときに、onCreateOptionsMenu() でメニューへの参照を保持していることです。アクション バーで何かを行う前に、この参照を確認するようにアクティビティ コードの残りの部分を変更しました。これでも非常に高速に実行され、ユーザー エクスペリエンスは影響を受けません。

同じことをして、初期化を延期することをお勧めします。

于 2012-12-16T17:39:11.203 に答える
0

これをアクティビティタグに追加します。

android:configChanges="orientation"

このメソッドをオーバーライドします。

public void onConfigurationChanged(Configuration newConfig)

詳細については、次を参照してください。

http://developer.android.com/reference/android/app/Activity.html#onConfigurationChanged(android.content.res.Configuration

http://developer.android.com/reference/android/R.attr.html#configChanges

于 2012-12-18T16:52:39.470 に答える