在XE16中调用openOptionsMenu()会导致WindowManager.BadTokenException

时间:2014-04-16 08:18:10

标签: android google-glass google-gdk

我有一个GDK应用程序在XE12中运行良好但现在在转换到GDK后在XE16中崩溃:19。特别是,在活动中调用openOptionsMenu()(在这种情况下,打开Live Card上的选项菜单)会导致BadTokenExceptions。

Logcat输出:

04-16 03:36:43.197: E/AndroidRuntime(2465): FATAL EXCEPTION: main
04-16 03:36:43.197: E/AndroidRuntime(2465): Process: com.voidstar.glass.sample.pinDrop, PID: 2465
04-16 03:36:43.197: E/AndroidRuntime(2465): java.lang.RuntimeException: Unable to resume activity {com.voidstar.glass.sample.pinDrop/com.voidstar.glass.sample.pinDrop.MenuActivity}: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2828)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2857)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2290)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.app.ActivityThread.access$800(ActivityThread.java:138)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1236)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.os.Handler.dispatchMessage(Handler.java:102)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.os.Looper.loop(Looper.java:149)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.app.ActivityThread.main(ActivityThread.java:5061)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at java.lang.reflect.Method.invokeNative(Native Method)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at java.lang.reflect.Method.invoke(Method.java:515)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:794)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:610)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at dalvik.system.NativeStart.main(Native Method)
04-16 03:36:43.197: E/AndroidRuntime(2465): Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.view.ViewRootImpl.setView(ViewRootImpl.java:561)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:259)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at com.android.internal.policy.impl.PhoneWindow.openPanel(PhoneWindow.java:693)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at com.android.internal.policy.impl.PhoneWindow.openPanel(PhoneWindow.java:555)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.app.Activity.openOptionsMenu(Activity.java:2878)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at com.voidstar.glass.sample.pinDrop.MenuActivity.onResume(MenuActivity.java:71)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1194)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.app.Activity.performResume(Activity.java:5316)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2818)
04-16 03:36:43.197: E/AndroidRuntime(2465):     ... 12 more

将活动绑定到Live Card的服务方法:

@Override
    public int onStartCommand(Intent intent, int flags, int startId) {      
        // This method is called whenever the Glassware is invoked via voice commands or the OK Glass menu.
        if (mLiveCard == null) {    
            Log.d(TAG, "Connecting mLocationManager");

            Criteria criteria = new Criteria();
            criteria.setAccuracy(Criteria.ACCURACY_COARSE);

            PinDropLocationListener listener = new PinDropLocationListener();
            locationListeners.add(listener);
            mLocationManager.requestSingleUpdate(criteria, listener, null);

            mLiveCard = new LiveCard(getBaseContext(), LIVE_CARD_TAG);
            mLiveCard.setViews(new RemoteViews(getPackageName(), R.layout.activity_waiting));
            mLiveCard.attach(this); // Prevent this Service from being killed to free up memory

            Intent menuIntent = new Intent(this, MenuActivity.class); // Since menus can only be attached to Activities, we create an activity to own and launch the menu.
            menuIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
            mLiveCard.setAction(PendingIntent.getActivity(this, 0, menuIntent, 0)); // This Intent will be fired whenever the LiveCard is tapped.

            Log.d(TAG, "Publishing LiveCard");
            mLiveCard.publish(PublishMode.REVEAL); // Add the LiveCard to the Timeline and switch to it
            Log.d(TAG, "Done publishing LiveCard");
        } else {
            mLiveCard.navigate(); // Switch to the app if it's already running
        }

        return START_STICKY; // No idea what this does. Your guess is as good as mine.
    }

有问题的活动:

/**
 * Activity showing the options menu.
 */
public class MenuActivity extends Activity {
    // This is technically an Immersion!
    // Because Services have no UI, we need to open this Activity, which in turn opens its menu!

    PinDropService.MenuBinder mBinder;

    private static String TAG = "PinDropMenu";

    boolean hasLocation;

    /*
     * Links this Activity to the Service that spawned it, so the Menu can send and receive information
     */
    private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            if (service instanceof PinDropService.MenuBinder) {
                mBinder = (PinDropService.MenuBinder)service;
                hasLocation = mBinder.hasLocation();
                Log.d(TAG, hasLocation ? "Received has location" : "Received no location");
                //openOptionsMenu();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {}
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        bindService(new Intent(this, PinDropService.class), mConnection, 0);
    }

    @Override
    public void onResume() {
        super.onResume();
        openOptionsMenu();
    }

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

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        if (!hasLocation) {
            menu.findItem(R.id.directions).setVisible(false);
            menu.findItem(R.id.remember).setVisible(false);
        }
        else {
            menu.findItem(R.id.directions).setVisible(true);
            menu.findItem(R.id.remember).setVisible(true);
        }
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle item selection.
        switch (item.getItemId()) {
            case R.id.directions:
                mBinder.startNavigation();
                return true;
            case R.id.remember:
                mBinder.addToTimeline(); // TODO: Add Mirror functionality!
                return true;
            case R.id.stop: // IT IS CRITICALLY IMPORTANT TO ADD THIS OR THE GLASSWARE CAN'T BE KILLED IN USERSPACE!
                stopService(new Intent(this, PinDropService.class));
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public void onOptionsMenuClosed(Menu menu) {
        // Nothing else to do, closing the Activity.
        finish();
    }

    @Override
    public void onStop() {
        super.onStop();
        unbindService(mConnection); // Don't leak Services!
    }
}

点击Live Card会导致立即崩溃,并转储上面的Logcat输出。奇怪的是,如果注释掉的openOptionsMenu()被取消注释并且现有的openOptionsMenu()被注释掉,则第一次点击将实际打开菜单。打开菜单的第二次尝试将失败,具有类似的Logcat输出(BadTokenException是主要异常,而不是RuntimeException上的内部异常)。

2 个答案:

答案 0 :(得分:5)

skylight1中的dario答案,

“样本也已更新 - 来自指南针:”

@Override
public void onAttachedToWindow() {
    super.onAttachedToWindow();
    mAttachedToWindow = true;
    openOptionsMenu();
}

TY dario!

答案 1 :(得分:4)

As petey said除了 onAttachedToWindow()之外,您还需要覆盖onResume() 。我的代码现在看起来像:

private boolean isAttached = false;

@Override
public void onAttachedToWindow() {
  super.onAttachedToWindow();
  this.isAttached = true;
  openOptionsMenu();
}

@Override
public void onResume() {
  super.onResume();
  if (this.isAttached)
    openOptionsMenu();
}