I have a clock widget Android app which I am now trying to update to API 26 requirements.
Up to now I used a background service which registered upon start in its onCreate
method a BroadcastReceiver
to receive system broadcasts, such as android.intent.action.SCREEN_ON, android.intent.action.SCREEN_OFF, android.intent.action.TIME_SET, android.intent.action.TIMEZONE_CHANGED
. This service was then pausing the clock while screen is off and waking it up when screen is back on to save the battery.
In Oreo a service of this kind does not seem to be an option, because it would have to run in the foreground with a notification which really has no significance for the user. Also, as far as I have seen in the documentation, JobScheduler
cannot help me either as I have not found that it is possible to schedule a job to when the screen is on.
I tried creating a BroadcastReceiver
within the AppWidgetProvider
class, and registering it in the AppWidgetProvider
's onUpdate
method to receive the said system broadcasts. This works well and broadcasts do get received, but only until the screen remains off for a period of time; afterwards, it seems that the app gets killed by the system somehow, or otherwise stops working without any reported error or a crash; however still if I click it, it will open the configuration activity as normal.
My questions:
How do I properly listen to screen on/off broadcasts on API 26+ if I do not want to run a foreground service?
Is it possible to listen to system broadcasts from the
AppWidgetProvider
class itself, by registering aBroadcastReceiver
within it, or by even registeringAppWidgetProvider
itself to receive system events (anywayAppWidgetProvider
is an extension ofBroadcastReceiver
).Why does my
AppWidgetProvider
aparently stop receiving broadcasted system intents after some sleep period?
EDIT:
I found the following in the Android documentation for registerReceiver
method which appears to be the answer to my questions 2 and 3.
Note: this method cannot be called from a BroadcastReceiver component; that is, from a BroadcastReceiver that is declared in an application's manifest. It is okay, however, to call this method from another BroadcastReceiver that has itself been registered at run time with registerReceiver(BroadcastReceiver, IntentFilter), since the lifetime of such a registered BroadcastReceiver is tied to the object that registered it.
I would conclude that my use and registration of a BroadcastReceiver
inside the AppWidgetProvider
was contrary to this specification.
I will leave this post open because others may find this information useful and my question 1 still remains valid.
Here what I am doing for listen SCREEN_OFF and SCREEN_ON broadcast in Android API 26 (Oreo) and above. This answer is not related to widget but it may be helpful to find some work-around.
I am using Job Scheduler for register and unRegister Broadcast Receiver which listen SCREEN_OFF and SCREEN_ON action.
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.PowerManager;
import android.support.annotation.NonNull;
import android.util.Log;
import com.evernote.android.job.Job;
import com.evernote.android.job.JobManager;
import com.evernote.android.job.JobRequest;
import java.util.concurrent.TimeUnit;
public class LockScreenJob extends Job {
private static final String TAG = LockScreenJob.class.getSimpleName();
public static final String TAG_P = "periodic_job_tag";
public static final String TAG_I = "immediate_job_tag";
//Used static refrence of broadcast receiver for ensuring if it's already register or not NULL
// then first unregister it and set to null before registering it again.
public static UnlockReceiver aks_Receiver = null;
@Override
@NonNull
protected Result onRunJob(Params params) {
// run your job here
String jobTag = params.getTag();
if (BuildConfig.DEBUG) {
Log.i(TAG, "Job started! " + jobTag);
}
PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
boolean isInteractive = false;
// Here we check current status of device screen, If it's Interactive then device screen is ON.
if (Build.VERSION.SDK_INT >= 20) {
isInteractive = pm.isInteractive();
} else {
isInteractive = pm.isScreenOn();
}
try {
if (aks_Receiver != null) {
getContext().getApplicationContext().unregisterReceiver(aks_Receiver); //Use 'Application Context'.
}
} catch (Exception e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
} finally {
aks_Receiver = null;
}
try {
//Register receiver for listen "SCREEN_OFF" and "SCREEN_ON" action.
IntentFilter filter = new IntentFilter("android.intent.action.SCREEN_OFF");
filter.addAction("android.intent.action.SCREEN_ON");
aks_Receiver = new UnlockReceiver();
getContext().getApplicationContext().registerReceiver(aks_Receiver, filter); //use 'Application context' for listen brodcast in background while app is not running, otherwise it may throw an exception.
} catch (Exception e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
}
if (isInteractive)
{
//TODO:: Can perform required action based on current status of screen.
}
return Result.SUCCESS;
}
/**
* scheduleJobPeriodic: Added a periodic Job scheduler which run on every 15 minute and register receiver if it's unregister. So by this hack broadcast receiver registered for almost every time w.o. running any foreground/ background service.
* @return
*/
public static int scheduleJobPeriodic() {
int jobId = new JobRequest.Builder(TAG_P)
.setPeriodic(TimeUnit.MINUTES.toMillis(15), TimeUnit.MINUTES.toMillis(5))
.setRequiredNetworkType(JobRequest.NetworkType.ANY)
.build()
.schedule();
return jobId;
}
/**
* runJobImmediately: run job scheduler immediately so that broadcasr receiver also register immediately
* @return
*/
public static int runJobImmediately() {
int jobId = new JobRequest.Builder(TAG_I)
.startNow()
.build()
.schedule();
return jobId;
}
/**
* cancelJob: used for cancel any running job by their jobId.
* @param jobId
*/
public static void cancelJob(int jobId) {
JobManager.instance().cancel(jobId);
}
}
And my JobCrator class LockScreenJobCreator is:
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.evernote.android.job.Job;
import com.evernote.android.job.JobCreator;
public class LockScreenJobCreator implements JobCreator {
@Override
@Nullable
public Job create(@NonNull String tag) {
switch (tag) {
case LockScreenJob.TAG_I:
return new LockScreenJob();
case LockScreenJob.TAG_P:
return new LockScreenJob();
default:
return null;
}
}
}
BroadcastReceiver class UnlockReceiver is :
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class UnlockReceiver extends BroadcastReceiver {
private static final String TAG = UnlockReceiver.class.getSimpleName();
@Override
public void onReceive(Context appContext, Intent intent) {
if (BuildConfig.DEBUG) {
Log.i(TAG, "onReceive: " + intent.getAction());
}
if (intent.getAction().equalsIgnoreCase(Intent.ACTION_SCREEN_OFF))
{
//TODO:: perform action for SCREEN_OFF
} else if (intent.getAction().equalsIgnoreCase(Intent.ACTION_SCREEN_ON)) {
//TODO:: perform action for SCREEN_ON
}
}
}
And adding JobCreator class to the Application class like this:
public class AksApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
JobManager.create(this).addJobCreator(new LockScreenJobCreator());
//TODO: remaing code
}
}
Don't forget to define application class in your AndroidManifest.xml
After all this I start Job scheduler from my Activity like this:
import android.support.v7.app.AppCompatActivity;
public class LockScreenActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
runJobScheduler();
//TODO: other code
}
@Override
protected void onStop() {
super.onStop();
cancelImmediateJobScheduler();
//TODO: other code
}
/**
* runJobScheduler(): start immidiate job scheduler and pending job schedulaer from
your main Activity.
*/
private void runJobScheduler() {
Set<JobRequest> jobSets_I = null, jobSets_P = null;
try {
jobSets_I = JobManager.instance().getAllJobRequestsForTag(LockScreenJob.TAG_I);
jobSets_P = JobManager.instance().getAllJobRequestsForTag(LockScreenJob.TAG_P);
if (jobSets_I == null || jobSets_I.isEmpty()) {
LockScreenJob.runJobImmediately();
}
if (jobSets_P == null || jobSets_P.isEmpty()) {
LockScreenJob.scheduleJobPeriodic();
}
//Cancel pending job scheduler if mutiple instance are running.
if (jobSets_P != null && jobSets_P.size() > 2) {
JobManager.instance().cancelAllForTag(LockScreenJob.TAG_P);
}
} catch (Exception e) {
if (Global_Var.isdebug) {
e.printStackTrace();
}
} finally {
if (jobSets_I != null) {
jobSets_I.clear();
}
if (jobSets_P != null) {
jobSets_P.clear();
}
jobSets_I = jobSets_P = null;
}
}
/**
* cancelImmediateJobScheduler: cancel all instance of running job scheduler by their
TAG name.
*/
private void cancelImmediateJobScheduler() {
JobManager.instance().cancelAllForTag(LockScreenJob.TAG_I);
}
}
By running Job Scheduler like this I am able to listen SCREEN_OFF and SCREEN_ON action without running any foreground or background service. I tested above code on API 26+ and it's working fine.
来源:https://stackoverflow.com/questions/48598200/screen-on-off-broadcast-listener-for-a-widget-on-android-oreo