PSA: Android 6.0 Theme.NoDisplay Regression
If you have been using Theme.NoDisplay
in one or more activities in
your app, and you have not tested them on Android 6.0 yet, I recommend
that you do so soon. An undocumented regression in Android 6.0 will
cause some of those activities to crash upon launch,
if your targetSdkVersion
is 23 or higher.
If the activity does all of its work in onCreate()
(e.g., starts up
a service), then calls finish()
, you seem to be OK.
But sometimes
the work that needs to be done is a bit more involved than that.
In particular, calling startActivityForResult()
, with an eye towards
calling finish()
in onActivityResult()
, will cause your app to crash
with an IllegalStateException
saying that your activity
“did not call finish() prior to onResume() completing”. This, apparently,
is a new requirement of Theme.NoDisplay
activities.
You might wonder why you need a Theme.NoDisplay
activity to be calling
startActivityForResult()
in the first place. The scenario I encountered
was trying to use the media projection APIs, specifically to set up
recording a screencast. To use the media projection APIs, you need to call
startActivityForResult()
on a system-supplied Intent
, then use the
result data to set up a MediaProjection
. In the case of this sample
app, the user interface was primarily going to be a foreground
Notification
of a service (plus some command-line scripts), so I had
no need for an activity, except as a means of enabling the media projection
APIs.
So, I have something like this:
package com.commonsware.android.andcorder;
import android.app.Activity;
import android.content.Intent;
import android.media.projection.MediaProjectionManager;
import android.os.Bundle;
public class MainActivity extends Activity {
private static final int REQUEST_SCREENSHOT=59706;
private MediaProjectionManager mgr;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mgr=(MediaProjectionManager)getSystemService(MEDIA_PROJECTION_SERVICE);
startActivityForResult(mgr.createScreenCaptureIntent(),
REQUEST_SCREENSHOT);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode==REQUEST_SCREENSHOT) {
if (resultCode==RESULT_OK) {
Intent i=
new Intent(this, RecorderService.class)
.putExtra(RecorderService.EXTRA_RESULT_CODE, resultCode)
.putExtra(RecorderService.EXTRA_RESULT_INTENT, data);
startService(i);
}
}
finish();
}
}
This works just fine with Theme.NoDisplay
on Android 5.1, passing control
to my RecorderService
to actually manage starting and stopping the screen
recording. On Android 6.0, the app crashes with the aforementioned exception.
Another scenario is using requestPermissions()
from the
Android 6.0 runtime permission system.
Under the covers, requestPermissions()
uses startActivityForResult()
.
If you have an app where the UI is mostly not an activity (e.g., app
widget, Notification
, wear app), but you need runtime permissions,
you might try creating a Theme.NoDisplay
activity just to call
requestPermissions()
and handle onRequestPermissionsResult()
. I would
expect this to generate the same exception on Android 6.0, though I have
not tried this scenario to confirm the theory.
The issue
that was filed on the M Developer Preview about this ran
into the problem when trying to show a Dialog
from onCreate()
.
The issue went nowhere.
One fix described in that issue — calling setVisible(true)
in onStart()
— works, but you wind up with a visible activity
UI (black background and old-timey gray title bar).
What worked better for me was
to
use Theme.Translucent.NoTitleBar
as the theme on Android 6.0+.
If you want to stick with Theme.NoDisplay
on older devices, create
a custom theme that inherits from Theme.NoDisplay
in res/values/
but from Theme.Translucent.NoTitleBar
in res/values-v23/
, then use
your custom theme.
Or, go with targetSdkVersion
22 temporarily. Eventually, something will
drive you to using targetSdkVersion
of 23 or higher, though.
UPDATE: 2015-11-08 Dianne Hackborn
responded to this post,
explaining that Theme.NoDisplay
is somewhat
documented
to require a finish()
before onResume()
and outlines a bit of the
rationale for the change to detect this case and fail fast. She and I
disagree on what constitutes a regression (IMHO, a substantial change
in behavior is a regression, even if documentation hints at the validity
of the change), but it is great that we have
a more-or-less official source for what is going on.
Many thanks to the Android Weekly newsletter for happening to mention Ms. Hackborn’s post in this week’s issue.