Android Oreo中广播接收器不起作用。

19

我的广播接收器在Oreo上不起作用,但是在Oreo以下版本上运行良好。我已经搜索了很多相关内容,但没有找到合适的解决方案。是否有人遇到了同样的问题?这是关于我的服务的代码,在其中实现了广播。请建议我如何使其在Oreo上工作。

这是类:

public int onStartCommand(Intent intent, int flags, int startId) {
        mContext = this;
        mAppPreferences = new AppPreferences(mContext);
        if (intent.getExtras() != null) {
            data = (String) intent.getExtras().get("showPopUp");
            phoneNumber= (String) intent.getExtras().get("dialNumber");
        }
        final IntentFilter intentFilter = new IntentFilter();
        if (data.equalsIgnoreCase("true")) {
            showPopup(getApplicationContext());
            Utils.ApiHit(phoneNumber,getApplicationContext());
        }
        intentFilter.setPriority(2147483647);
        intentFilter.addAction("android.intent.action.PHONE_STATE");
        callExplicitReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (intent.getAction().equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
                    if (intent.getAction().equals("android.intent.action.NEW_OUTGOING_CALL")) {
                        savedNumber = intent.getExtras().getString("android.intent.extra.PHONE_NUMBER");
                    } else {
                        String stateStr = intent.getExtras().getString(TelephonyManager.EXTRA_STATE);
                        phoneNumber = intent.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
                        int state = 0;
                        if (stateStr.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
                            state = TelephonyManager.CALL_STATE_IDLE;
                        } else if (stateStr.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) {
                            state = TelephonyManager.CALL_STATE_OFFHOOK;
                        } else if (stateStr.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
                            state = TelephonyManager.CALL_STATE_RINGING;
                        }
                        onCallStateChanged(context, state, phoneNumber);
                    }
                }
            }
        };
        mContext.registerReceiver(callExplicitReceiver, intentFilter);
        return START_NOT_STICKY;
    }


    public void onIncomingCallReceived(Context ctx, String number, Date start) {
    }

    public void onIncomingCallAnswered(Context ctx, String number, Date start) {
        if (popupView.getVisibility() == View.GONE) {
            popupView.setVisibility(View.VISIBLE);
        }
    }

    public void onIncomingCallEnded(Context ctx, String number, Date start, Date end) {

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                DeleteCallLogByNumber(number);
            }
        }, 2000);
        if (popupView.getVisibility() == View.VISIBLE) {
            popupView.setVisibility(View.GONE);
        }
    }

    public void onOutgoingCallStarted(Context ctx, String number, Date start) {
//        mAppPreferences.setPrefrenceString("busy", "yes");
//        if (data.equalsIgnoreCase("true")) {
            mediaPlayer = MediaPlayer.create(ctx, R.raw.speech_audio);
//        } else {
//            mediaPlayer = MediaPlayer.create(ctx, R.raw.speech_audio);
//        }

        mediaPlayer.start();
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                if (mediaPlayer != null && mediaPlayer.isPlaying()) {
                    mediaPlayer.stop();
                    mediaPlayer.release();
                }
            }
        }, 12000);
        if (popupView.getVisibility() == View.GONE) {
            popupView.setVisibility(View.VISIBLE);
        }
    }


    public void onOutgoingCallEnded(Context ctx, String number, Date start, Date end) {
        mAppPreferences.setPrefrenceString("busy", "no");
        if (mediaPlayer != null && mediaPlayer.isPlaying()) {
            mediaPlayer.stop();
            mediaPlayer.release();
            mediaPlayer = null;
        }
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                DeleteCallLogByNumber(phoneNumber);
            }
        }, 2000);
        if (popupView.getVisibility() == View.VISIBLE) {
            popupView.setVisibility(View.GONE);
        }
    }

    public void onMissedCall(Context ctx, String number, Date start) {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                DeleteCallLogByNumber(phoneNumber);
            }
        }, 2000);
        if (popupView.getVisibility() == View.VISIBLE) {
            popupView.setVisibility(View.GONE);
        }
    }




public void onCallStateChanged(Context context, int state, String number) {
        if (lastState == state) {
            return;
        }
        switch (state) {
            case TelephonyManager.CALL_STATE_RINGING:
                isIncoming = true;
                callStartTime = new Date();
                savedNumber = number;
                onIncomingCallReceived(context, number, callStartTime);
                break;
            case TelephonyManager.CALL_STATE_OFFHOOK:
                if (lastState != TelephonyManager.CALL_STATE_RINGING) {
                    isIncoming = false;
                    callStartTime = new Date();
                    onOutgoingCallStarted(context, savedNumber, callStartTime);
                } else {
                    isIncoming = true;
                    callStartTime = new Date();
                    onIncomingCallAnswered(context, savedNumber, callStartTime);
                }
                break;
            case TelephonyManager.CALL_STATE_IDLE:
                if (popupView.getVisibility() == View.VISIBLE) {
                    popupView.setVisibility(View.GONE);
                }
                if (lastState == TelephonyManager.CALL_STATE_RINGING) {
                    onMissedCall(context, savedNumber, callStartTime);
                } else if (isIncoming) {
                    onIncomingCallEnded(context, savedNumber, callStartTime, new Date());
                } else {
                    onOutgoingCallEnded(context, savedNumber, callStartTime, new Date());
                }
                break;
        }
        lastState = state;
    }

    @Override
    public void onDestroy() {
        mContext.unregisterReceiver(callExplicitReceiver);
    }

收件人没有收到任何内容,请问有人能帮我解决这个问题吗?

根据讨论新增内容

清单数据:

使用的权限:

 <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

接收方

<receiver android:name="com.example.dialer.AppUtils.StartUpBootReceiver" android:enabled="true" android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </receiver>

我的广播接收器类:-

public class StartUpBootReceiver extends BroadcastReceiver {

    private Context mContext;

    @Override
    public void onReceive(Context context, Intent intent) {
        mContext= context;
        String action = "START";

        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
            {
                context.startForegroundService(new Intent(context, PhoneStateService.class));
            }
            else
            {
                context.startService(new Intent(context, PhoneStateService.class));
            }
        }
    }


    private boolean isServiceRunning(Class<?> serviceClass) {
        ActivityManager manager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
        for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
            if (serviceClass.getName().equals(service.service.getClassName())) {
                return true;
            }
        }
        return false;
    }
}

Rest服务将会被调用,但问题是接收方仍然没有接到电话。我的主要观点是,服务只应该在用户点击按钮时被调用一次,而不是自动调用,因为我必须在服务中传递一些值。

谢谢


我收到了你的请求;谷歌上没有好的文档,也没有示例实现的例子。但是我已经在奥利奥上实现了它,并且甚至在即将推出的安卓P上进行了测试;一切正常... - sandhya sasane
我建议现在不要实现任何代码:将其分解为任务... 1)仅通过在manifest.xml中注册来实现启动完成接收器 2)从接收器调用您的服务,该服务将仅打印hello 3)上传上述代码与manifest.xml; 在此处提供接收器和服务代码,并让我查看它 4)然后在服务中实现运行时广播接收器以接收呼叫广播... - sandhya sasane
不一定问题只是 START_NOT_STICKY ... 但它必须是 START_STICKY,以便运行时广播接收器始终为您保留。 - sandhya sasane
有很多事情是相互关联的... 1)如果服务是STICKY,则必须通知ONGOING通知;2)清单接收器现在受到限制,因此只需实现BOOT COMPLETE接收器并从服务注册其余接收器。 - sandhya sasane
这对我有用:(https://stackoverflow.com/questions/52364398/broadcast-receiver-for-nought-and-oreo-devices-not-working) - quilkin
显示剩余16条评论
6个回答

19

广播限制

如果一个应用程序注册接收广播,每次发送广播时应用的接收器都会消耗资源。如果太多的应用程序基于系统事件注册接收广播,那么触发广播的系统事件就会导致所有这些应用程序在短时间内消耗资源,从而影响用户体验。为了缓解这个问题,Android 7.0 (API 级别25) 在后台优化中描述的广播上设置了限制。Android 8.0 (API 级别26) 加强了这些限制。

  1. 针对 Android 8.0 或更高版本的应用不再能够在其清单文件中注册隐式广播接收器。隐式广播是指不特别针对该应用程序的广播。例如,ACTION_PACKAGE_REPLACED 是一个隐式广播,因为它发送给所有已注册的监听器,让它们知道设备上某个包已被替换。但是,ACTION_MY_PACKAGE_REPLACED 不是隐式广播,因为它只发送给包被替换的应用程序,无论有多少其他应用程序已为该广播注册了监听器。

  2. 应用程序仍然可以在其清单文件中注册显式广播接收器。

  3. 应用程序可以在运行时使用 Context.registerReceiver() 注册接收器以接收任何广播,无论是隐式或显式广播。

  4. 需要签名权限的广播不受此限制,因为这些广播仅发送给已使用相同证书签名的应用程序,而不是设备上所有应用程序。

来自官方文档


3
请看这个链接:https://commonsware.com/blog/2017/04/11/android-o-implicit-broadcast-ban.html。Android O系统已经禁止隐式广播,详情请查看链接。 - Mayank Bhatnagar
1
@MayankBhatnagar,某些意图操作被排除在禁令之外。 - exploitr
1
如果静态广播接收器的 action 值是任意的,那么它会成为隐式接收器还是显式接收器?@MayankBhatnagar - Neeraj Sewani
1
如果我们在Android Oreo中使用未被允许的广播,会发生什么?它会无法工作还是应用程序会崩溃? - Rajesh K
@RajeshK 它不会崩溃。只是BroadcastReceiver将无法接收Intent。 - hidd
显示剩余3条评论

5
我是一款有用的助手,可以为你翻译文本。
我也曾经遇到过这样的问题,但是我找到了更好的解决方案: 类 MyReceiver
@BroadcastReceiverActions({
        "android.intent.action.SCREEN_ON",
        "android.intent.action.SCREEN_OFF",
        "android.intent.action.DREAMING_STARTED",
        "android.intent.action.DREAMING_STOPPED",
        "android.intent.action.ACTION_POWER_DISCONNECTED",
        "android.intent.action.ACTION_POWER_CONNECTED",
        "android.net.conn.CONNECTIVITY_CHANGE"
})
public class MyReceiver extends BroadcastReceiver {

    public MyReceiver() {
        super();
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        Session.getGlobalReceiverCallBack(context, intent);

        //Log.e("dfd", "" + intent.getAction());
    }
}

应用程序控制器类

public class AppController extends Application {

    private BroadcastReceiver receiver;
    MyReceiver mR;

    @Override
    public void onCreate() {
        super.onCreate();
        mR = new MyReceiver();
        receiver = DynamicReceiver.with(mR)
                .register(this);

    }
}

主活动类 MainActivity

public class MainActivity extends AppCompatActivity implements GlobalReceiverCallBack {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Session.setmGlobalReceiverCallback(this);

    }

    @Override
    public void onCallBackReceived(Context context, Intent intent) {

        Toast.makeText(context, "" + intent.getAction(), Toast.LENGTH_LONG).show();
    }
}

完整的参考资料可以查看https://github.com/devggaurav/BroadcastReceiver-For-Naught-and-Oreo-devices


当应用程序被杀死时,上述链接代码无法正常工作。 - mdroid

5

问题出在您正在尝试运行的服务上,服务或持久化后台服务不允许长时间运行针对Oreo及以上版本的应用程序。

请查看此指南这个链接以迁移您的应用程序以支持Oreo。


1
你能提供除了谷歌开发者网站之外的任何参考资料吗?他们只是提供了一些提示,没有实时的例子。因为自2018年11月以来,我在三星和NEXUS设备上一直面临着这个后台崩溃问题。为什么谷歌没有从开发者的角度考虑,让我们陷入这样的麻烦呢?这是我现在最大的担忧。 - MohanRaj S

1

Android 8.0提供了多项改进,使得JobScheduler更容易用定时任务替代服务和广播接收器:

https://developer.android.com/about/versions/oreo/background

在许多情况下,以前注册隐式广播的应用程序可以通过使用JobScheduler作业获得类似的功能。例如,社交照片应用可能需要定期清理其数据,并希望在设备连接到充电器时进行此操作。以前,该应用在其清单中注册了一个接收器来处理ACTION_POWER_CONNECTED广播;当应用程序接收到该广播时,它会检查是否需要清理。为了迁移到Android 8.0或更高版本,该应用将从其清单中删除该接收器。相反,该应用程序安排了一个清理作业,在设备处于空闲和充电状态时运行。

1
在activity的onCreate方法中注册广播接收器,而不是在清单文件中注册,并在destroy方法中取消注册。希望这在Android 9上能够起作用。

寻找静态广播接收器。 - lava

0

在实现通话录音应用程序时,我遇到了类似的问题,

我将以下代码添加到AndroidManifest.xml文件中,然后注册正常工作

         <receiver android:name=".Services.Receiver"
             android:permission="android.permission.RECEIVE_BOOT_COMPLETED"
             android:enabled="true"
             android:exported="true">
            <intent-filter>

                <action android:name="android.intent.action.PHONE_STATE"/>
                <action android:name="android.intent.action.ANSWER"/>
                <action android:name="android.intent.action.CALL_BUTTON"/>
                <action android:name= "android.intent.action.NEW_OUTGOING_CALL"/>
                <action android:name="android.intent.action.BOOT_COMPLETED" />

            </intent-filter>
         </receiver>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接