In the end I pulled the old activity trick out the bag and have a solution that is acceptable. Documenting it here for posterity, but love to hear if someone comes up with a better way.
First of call, create a blank ViewPage that does nothing much but says "This view intentionally left blank" or some other useful information for the user so they know that the upgrade was successful, just that the view can be closed. In our case we explain where to find the equivalent functionality now and have a link that they can click on that will take them to that part of the application.
Second, register this view class as the implementation of the old view ID, and mark it as not restorable, i.e.
<view
allowMultiple="false"
category="com.foobar.category"
class="com.foobar.ui.views.DeprecatedView"
id="com.foobar.ui.views.OldView"
name="My Old View"
restorable="false"/>
Finally, we need to pull the activity trick out of the bag. Create a new activity and put your view into it. This will filter your old view out of the Show View, Other... Dialog so that new users won't see it again. For example:
<extension
point="org.eclipse.ui.activities">
<activity
name="Deprecated"
description="Supress old views that are no longer used."
id="com.foobar.hidden">
</activity>
<activityPatternBinding
activityId="com.foobar.hidden"
pattern="com.foorbar.plugin/com.foobar.ui.views.OldView"
isEqualityPattern="true">
</activityPatternBinding>
</extension>
The user experience is that after upgrade, if the old view was visible then the user gets a nice message explaining why and telling them what to do (i.e. close the old view and move along). Either way, when they close Eclipse and restart that view is no longer displayed in the perspective. New users never see the view or any hints of it in the Show View dialog so after a couple of releases when I'm confident everyone has upgraded I can remove the deprecated view registration from the plugin.xml etc.
It's the best experience I could come up with - but happy to hear if people think of a better one. If you want to read more about Eclipse activity filtering then take a look here - it's the only way I could find for filtering a registered view when I stepped through the ViewRegistry and ShowViewDialog code.