我有一个时钟小部件Android应用程序,我现在正在尝试更新到API 26要求。
到目前为止,我使用了一个后台服务,该服务在其onCreate
方法BroadcastReceiver
开始时注册,以接收系统广播,例如android.intent.action.SCREEN_ON, android.intent.action.SCREEN_OFF, android.intent.action.TIME_SET, android.intent.action.TIMEZONE_CHANGED
。当屏幕关闭时,此服务暂停时钟并在屏幕重新启动时将其唤醒以节省电池。
在Oreo中,这种服务似乎不是一种选择,因为它必须在前台运行,并且通知对用户没有任何意义。另外,据我在文档中看到,JobScheduler
无法帮助我,因为我还没有发现可以在屏幕开启时安排作业。
我尝试在BroadcastReceiver
类中创建AppWidgetProvider
,并在AppWidgetProvider
的{{1}}方法中注册它以接收所述系统广播。这很好用,广播确实收到了,但直到屏幕保持关闭一段时间;之后,似乎应用程序以某种方式被系统杀死,或者在没有任何报告错误或崩溃的情况下停止工作;但是,如果我点击它,它将正常打开配置活动。
我的问题:
如果我不想运行前台服务,如何在API 26+上正确收听屏幕开/关广播?
是否可以通过onUpdate
类本身监听系统广播,在其中注册AppWidgetProvider
,甚至注册BroadcastReceiver
本身来接收系统事件(无论如何AppWidgetProvider
是AppWidgetProvider
)的扩展名。
为什么我的BroadcastReceiver
在一段睡眠期后才会停止接收广播的系统意图?
修改
我在AppWidgetProvider
方法的Android文档中找到以下内容,这似乎是我的问题2和3的答案。
注意:无法从BroadcastReceiver组件调用此方法; 也就是说,来自在应用程序中声明的BroadcastReceiver 表现。但是,从另一个方法调用此方法是可以的 BroadcastReceiver本身已在运行时注册 registerReceiver(BroadcastReceiver,IntentFilter),自生命以来 这样一个已注册的BroadcastReceiver与该对象绑定在一起 注册了它。
我会得出结论,我在registerReceiver
内使用和注册BroadcastReceiver
与此规范相反。
我会将这篇文章公开,因为其他人可能会发现此信息有用,我的问题1仍然有效。
答案 0 :(得分:5)
这是我正在做的工作,以监听Android API 26(Oreo)及更高版本中的SCREEN_OFF和SCREEN_ON。该答案与小部件无关,但是找到一些解决方法可能会有所帮助。
我正在使用Job Scheduler进行注册和 unRegister 广播接收器,它们监听SCREEN_OFF和SCREEN_ON动作。
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);
}
}
我的JobCrator类 LockScreenJobCreator 是:
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类 UnlockReceiver 是:
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
}
}
}
将JobCreator类添加到Application类中,如下所示:
public class AksApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
JobManager.create(this).addJobCreator(new LockScreenJobCreator());
//TODO: remaing code
}
}
不要忘记在 AndroidManifest.xml
中定义应用程序类所有这些之后,我从活动中启动Job Scheduler,如下所示:
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);
}
}
通过像这样运行Job Scheduler,我可以在不运行任何前台或后台服务的情况下侦听SCREEN_OFF和SCREEN_ON动作。我在API 26+上测试了以上代码,并且工作正常。