java - Android Oreo 上小部件的屏幕开/关广播监听器

标签 java android broadcastreceiver appwidgetprovider

我有一个时钟小部件 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 也无法帮助我,因为我还没有发现可以在屏幕打开时安排作业。

我尝试在 AppWidgetProvider 类中创建一个 BroadcastReceiver,并在 AppWidgetProvideronUpdate 中注册它接收所述系统广播的方法。这很有效,确实可以收到广播,但只能在屏幕关闭一段时间后才能收到;之后,似乎应用程序以某种方式被系统杀死,或者以其他方式停止工作而没有报告任何错误或崩溃;但是,如果我单击它,它将照常打开配置 Activity 。

我的问题:

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

  2. 是否可以通过在 AppWidgetProvider 类本身中注册一个 BroadcastReceiver 或注册 AppWidgetProvider< 来监听系统广播 本身接收系统事件(无论如何 AppWidgetProviderBroadcastReceiver 的扩展)。

  3. 为什么我的 AppWidgetProvider 在 hibernate 一段时间后明显停止接收广播的系统 Intent ?

编辑:

我在 Android 文档中找到了以下关于 registerReceiver 方法的内容,这似乎是我的问题 2 和 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.

我会得出结论,我在 AppWidgetProvider 中使用和注册 BroadcastReceiver 违反了此规范。

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

最佳答案

这是我在 Android API 26 (Oreo) 及更高版本中收听 SCREEN_OFF 和 SCREEN_ON 广播的操作。此答案与小部件无关,但可能有助于找到一些解决方法。

我正在使用 Job Scheduler 来监听 SCREEN_OFF 和 SCREEN_ON Action 的registerunRegister 广播接收器。

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

中定义应用程序类

在这一切之后,我从我的 Activity 中启动 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+ 上测试了上面的代码,它工作正常。

关于java - Android Oreo 上小部件的屏幕开/关广播监听器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48598200/

相关文章:

java - 在 servlet 和 web 服务之间共享静态对象

java - ZXing Barcode Scanner 如何兼容 Android 2.2 及以上版本

android - Orderbychild反向

android - 在android中通话期间来电号码?

java - Android:检测用户是否停止使用他的手机一段时间

java - GET 响应中包含 URL

java - playframework 中的级联删除 - 如何为实体建模

Android JUnit xml 报告未在设备上创建

java - 将字节数组和字符串存储在同一个文件中

java.lang.IllegalThreadStateException : Thread already started while there is only 1 thread 异常