屏幕开启/关闭Android Oreo上的小部件的广播监听器 [英] Screen on/off broadcast listener for a widget on Android Oreo

查看:258
本文介绍了屏幕开启/关闭Android Oreo上的小部件的广播监听器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a clock widget Android app which I am now trying to update to API 26 requirements.

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

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.

在奥利奥这样的服务似乎没有是一个选项,因为它必须在前台运行一个通知,这对用户来说真的没有意义。此外,据我在文档中看到, JobScheduler 也无法帮助我,因为我还没有发现可以在屏幕打开时安排作业。

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.

我尝试在 AppWidgetProvider 类中创建 BroadcastReceiver ,并在 AppWidgetProvider onUpdate 方法中注册,以接收所述系统广播。这很好用,广播确实收到,但只有在屏幕保持关闭一段时间之后;之后,似乎应用程序以某种方式被系统杀死,或者在没有任何报告错误或崩溃的情况下停止工作;但是,如果我点击它,它将正常打开配置活动。

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.

我的问题:


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

  1. How do I properly listen to screen on/off broadcasts on API 26+ if I do not want to run a foreground service?

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

Is it possible to listen to system broadcasts from the AppWidgetProvider class itself, by registering a BroadcastReceiver within it, or by even registering AppWidgetProvider itself to receive system events (anyway AppWidgetProvider is an extension of BroadcastReceiver).

为什么我的 AppWidgetProvider 显然停止接收广播的系统意图在一段睡眠期后?

Why does my AppWidgetProvider aparently stop receiving broadcasted system intents after some sleep period?

编辑:

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

I found the following in the Android documentation for registerReceiver method which appears to be the answer to my questions 2 and 3.

注意:无法从BroadcastReceiver组件调用此方法;
,来自在应用程序的
清单中声明的​​BroadcastReceiver。但是,可以从另一个
BroadcastReceiver调用此方法,该广播本身已在运行时使用
registerReceiver(BroadcastReceiver,IntentFilter)注册,因为这样一个已注册的BroadcastReceiver的生命周期
是绑定到
注册的对象。

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 与此规范相反。

I would conclude that my use and registration of a BroadcastReceiver inside the AppWidgetProvider was contrary to this specification.

我将离开此张贴开放,因为其他人可能会发现此信息有用,我的问题1仍然有效。

I will leave this post open because others may find this information useful and my question 1 still remains valid.

推荐答案

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

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.

我正在使用Job Scheduler进行注册取消注册收听SCREEN_OFF和SCREEN_ON操作的广播接收器。

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

我的JobCrator课 LockScreenJobCreator 是:

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类 UnlockReceiver 是:

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

}

将JobCreator类添加到Application类中,如下所示:

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
    }

}

不要忘记在 AndroidManifest.xml中定义应用程序类

Don't forget to define application class in your AndroidManifest.xml

之后所有这些我从我的Activity开始Job scheduler如下:

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

通过像这样运行Job Scheduler我能听SCREEN_OFF和SCREEN_ON操作,不运行任何前台或后台服务。我在API 26+上测试了上面的代码,它运行正常。

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.

这篇关于屏幕开启/关闭Android Oreo上的小部件的广播监听器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆