屏幕开/关广播监听器,用于Android Oreo上的小部件

我有一个时钟小部件Android应用程序,我现在正试图更新到API 26要求。

到目前为止,我使用了一个后台服务,该服务在其方法a中启动时注册以接收系统广播,例如.然后,此服务在屏幕关闭时暂停时钟,并在屏幕重新打开时将其唤醒以节省电池。onCreateBroadcastReceiverandroid.intent.action.SCREEN_ON, android.intent.action.SCREEN_OFF, android.intent.action.TIME_SET, android.intent.action.TIMEZONE_CHANGED

在奥利奥,这种服务似乎不是一种选择,因为它必须在前台运行,并发出对用户来说实际上没有意义的通知。另外,就我在文档中看到的那样,也无法帮助我,因为我没有发现可以将作业安排到屏幕打开时。JobScheduler

我尝试在类中创建一个,并在 中注册它以接收所述系统广播。这效果很好,广播确实会被接收,但只有在屏幕保持关闭一段时间之前。之后,该应用程序似乎以某种方式被系统杀死,或者以其他方式停止工作,而没有任何报告的错误或崩溃;但是,如果我单击它,它仍然会正常打开配置活动。BroadcastReceiverAppWidgetProviderAppWidgetProvideronUpdate

我的问题:

  1. 如果我不想运行前台服务,如何正确收听 API 26+ 上的屏幕开/关广播?

  2. 是否可以通过在其中注册一个,或者甚至通过注册自身来接收系统事件来收听来自类本身的系统广播(无论如何是 的扩展)。AppWidgetProviderBroadcastReceiverAppWidgetProviderAppWidgetProviderBroadcastReceiver

  3. 为什么我的家长在一段时间后停止接收广播的系统意图?AppWidgetProvider

编辑:

我在Android文档中找到了以下内容,似乎是我的问题2和3的答案。registerReceiver

注意:此方法不能从广播接收组件调用;也就是说,从应用程序清单中声明的广播接收机。但是,可以从另一个 BroadcastReceiver 调用此方法,该广播接收器本身已在运行时向 registerReceiver(BroadcastReceiver,IntentFilter)注册,因为此类已注册的 BroadcastReceiver 的生存期与注册它的对象相关联。

我的结论是,我对内部的使用和注册与此规范相悖。BroadcastReceiverAppWidgetProvider

我将保留此帖子,因为其他人可能会发现此信息很有用,并且我的问题1仍然有效。


答案 1

在这里,我正在为收听SCREEN_OFF所做的工作,并在Android API 26(Oreo)及以上版本中SCREEN_ON广播。这个答案与小部件无关,但找到一些解决方法可能会有所帮助。

我正在使用作业计划程序进行注册取消注册广播接收器,这些接收器侦听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 class 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

完成所有这些之后,我从我的活动中启动作业计划程序,如下所示:

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);
    }
}

通过像这样运行作业计划程序,我能够侦听SCREEN_OFF并SCREEN_ON操作,而无需运行任何前台或后台服务。我在API 26 +上测试了上面的代码,它工作正常。


答案 2

推荐