我有一个GDE应用程序在XE12中运行良好但现在在转换到GDK之后在XE16中崩溃:19.特别是,在Activity中调用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,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上的内部异常).
解决方法
As petey said,但除了onResume()之外,还需要覆盖onAttachedToWindow().我的代码现在看起来像:
private boolean isAttached = false; @Override public void onAttachedToWindow() { super.onAttachedToWindow(); this.isAttached = true; openOptionsMenu(); } @Override public void onResume() { super.onResume(); if (this.isAttached) openOptionsMenu(); }