Android中设备重启后广播接收器无法正常工作

时间:2017-01-07 17:40:32

标签: android broadcastreceiver android-broadcast android-broadcastreceiver

我已经检查了所有相关问题,但没有找到解决此问题的方法。所以这对我来说是一个绝对新的问题。

我有什么

我有一个Android应用程序,它在其清单中注册了几个广播接收器。这就是我的清单看起来的样子。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.app.myapp">

    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.USE_FINGERPRINT" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_CALL_LOG" />
    <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
    <uses-permission android:name="com.android.vending.BILLING" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />

    <uses-feature
        android:name="android.hardware.telephony"
        android:required="false" />

    <uses-feature
        android:name="android.hardware.screen.portrait"
        android:required="false" />

    <application
        android:name=".base.MyApp"
        android:allowBackup="false"
        android:icon="@drawable/ic_launcher"
        android:label="@string/label_app_name"
        android:largeHeap="true"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        tools:replace="label, allowBackup">

        <receiver android:name=".mics.BootReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.intent.action.QUICKBOOT_POWERON" />
            </intent-filter>
        </receiver>

        <receiver android:name=".PhoneCallReceiver">
            <intent-filter>
                <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
            </intent-filter>
        </receiver>

        <receiver
            android:name=".mics.DeviceAdminReceiver"
            android:permission="android.permission.BIND_DEVICE_ADMIN">
            <intent-filter>
                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
            </intent-filter>

            <meta-data
                android:name="android.app.device_admin"
                android:resource="@xml/device_admin" />
        </receiver>

        <receiver
            android:name="com.clevertap.android.sdk.InstallReferrerBroadcastReceiver"
            android:exported="true">
            <intent-filter>
                <action android:name="com.android.vending.INSTALL_REFERRER" />
            </intent-filter>
        </receiver>

        <meta-data
            android:name="com.app.myapp.utils.ImageLoaderModule"
            android:value="GlideModule" />

        <meta-data
            android:name="com.app.myapp.utils.AudioCoverLoaderModule"
            android:value="GlideModule" />

        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${applicationId}.provider"
            android:exported="false"
            android:grantUriPermissions="true">

            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths" />
        </provider>

        <activity
            android:name=".core.activities.SplashActivity"
            android:excludeFromRecents="true"
            android:label="@string/label_app_name"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
            </intent-filter>
        </activity>

        <activity-alias
            android:name=".core.activities.SplashActivity-Alias"
            android:icon="@drawable/ic_launcher"
            android:label="@string/label_app_name"
            android:noHistory="true"
            android:targetActivity="com.app.myapp.core.activities.SplashActivity">

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.MONKEY" />
            </intent-filter>

        </activity-alias>

        <activity
            android:name=".core.flow.authFlow.activities.AuthFlowActivity"
            android:excludeFromRecents="true"
            android:label="@string/label_app_name"
            android:screenOrientation="portrait" />

        <service android:name=".features.fileCloudSync.KillNotificationService" />

    </application>

</manifest>

还有10-15个其他活动,但为简单起见已删除。这是基本的启动接收器类。我从这里开始提供服务。

public class BootReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
            AlertUtils.showToast(context, "BOOT COMPLETED", Toast.LENGTH_LONG);
        }
    }
}

并且电话呼叫接收器类看起来像这样(它也已经简化),

public class PhoneCallReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
            AlertUtils.showToast(context, "PHONE CALL RECEIVED", Toast.LENGTH_LONG);
            // Simplified for brevity
        }
    }
}

问题

当我安装应用程序并启动一次时,所有这些接收器都能正常工作。但是在重新启动设备后,这些接收器根本无法工作。 BootCompleteReceiverPhoneCallReceiver都没有调用onReceive()方法。

我的假设是这些接收器会在重启后自动注册,但它不起作用。我需要BootCompleteReceiver才能工作,以便我可以在我的应用中启动重要服务。

我的观察

我已经彻底测试了这一点。重新启动设备后,接收器在我的 Nexus 5X(牛轧糖),Nexus 6P(牛轧糖),YU Yuphoria(棒棒糖)中正常工作,但在我的 OnePlus 3(Nougat)和Mi 4i中没有(棒棒糖)

相同的代码如何在少数设备上完美运行,而在其他设备上完全无法运行?我没有改变任何东西。

我在这里做错了什么?我的应用程序严重依赖于这些广播并基于这些启动服务。任何帮助将受到高度赞赏。

编辑1

为了更好地理解这个问题,我刚刚创建了一个非常小的测试项目,只有一个活动,并且完全相同BootCompleteReceiverPhoneCallReceiver

但奇怪的是,这个项目在我的OnePlus 3上完美运行,我的实际应用程序的接收器在重启后无法正常工作。我最初假设问题出在操作系统或设备中,但事实并非如此。

那么实际问题在哪里?它是在我的应用程序中(但它在其他设备上完美运行)或在操作系统和设备中(小型测试项目在同一操作系统和同一设备上运行良好)?

这让我很困惑。我需要一些专家帮助。

编辑2

我已经尝试过@shadygoneinsane给出的建议。以下是我的观察。

1)我试图通过ADB发送BOOT_COMPLETED广播。

./adb shell am broadcast -a android.intent.action.BOOT_COMPLETED -p com.app.myapp

我得到了这个堆栈跟踪,

Broadcasting: Intent { act=android.intent.action.BOOT_COMPLETED pkg=com.app.myapp }
java.lang.SecurityException: Permission Denial: not allowed to send broadcast android.intent.action.BOOT_COMPLETED from pid=25378, uid=2000
    at android.os.Parcel.readException(Parcel.java:1683)
    at android.os.Parcel.readException(Parcel.java:1636)
    at android.app.ActivityManagerProxy.broadcastIntent(ActivityManagerNative.java:3696)
    at com.android.commands.am.Am.sendBroadcast(Am.java:778)
    at com.android.commands.am.Am.onRun(Am.java:404)
    at com.android.internal.os.BaseCommand.run(BaseCommand.java:51)
    at com.android.commands.am.Am.main(Am.java:121)
    at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
    at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:276)

也许是因为我的设备没有扎根。我无法以任何方式发送此广播。

2)之后我尝试使用PROCESS_OUTGOING_CALLS广播。

./adb shell am broadcast -a android.intent.action.PROCESS_OUTGOING_CALLS -p com.app.myapp

我明白了,

Broadcasting: Intent { act=android.intent.action.PROCESS_OUTGOING_CALLS pkg=com.app.myapp }
Broadcast completed: result=0

似乎广播成功,但我没有看到任何Toast或任何日志。然后我打开拨号器拨号,然后我可以看到Toast和日志。

所以似乎通过ADB发送广播不起作用,但实际上打开拨号器并拨打了号码。

编辑3

根据@ChaitanyaAtkuri的建议,我也尝试为intent-filters添加优先级,但这种方法效果不佳。

我使用了500,999甚至最高整数值的优先级,但没有任何效果。我的一些朋友应用程序也会出现此问题。它们在某些设备中工作,而在其他设备中不起作用。

编辑4

终于找到了我的OnePlus 3中发生问题的根本原因。我的OnePlus 3最近更新为Nougat,他们推出了类似于Mi设备的功能,可防止某些应用在重启后自动启动。

禁用此功能后,我的应用程序在完全重启后开始接收广播。但这仍然没有解释两件事。

1)我的小测试项目在AutoLaunch应用程序列表中自动列入白名单,这就是它按预期工作的原因。但这怎么可能呢?操作系统为什么认为这个小应用程序值得自动启动?

2)有一些应用程序,如LockDown Pro,500 Firepaper,在AutoLaunch应用程序屏幕中列入黑名单,但仍然会在我的OnePlus 3和Mi 4i重启后接收广播。现在怎么可能?以某种方式可以以编程方式允许我的应用程序在这些设备(OnePlus和Mi)中自动启动吗?

编辑5

我已经尝试过@Rahul Chowdhury提出的解决方案,它似乎确实很有效。添加辅助功能服务后,问题就会重新解决。

但是,如果用户在授予后撤销了辅助功能权限,那么我是否可以通过编程方式检查我的应用是否可以使用辅助功能?

7 个答案:

答案 0 :(得分:30)

这是您提到的设备OnePlus和Mi的经过测试和运行的解决方案。

正如您所说, OnePlus Mi 设备上的自动启动防止功能会阻止应用在启动完成时自动启动其服务,从而改善整体设备启动速度和电池性能。但是,即使启用此功能,也有一种解决方法可让您的应用运行。

我注意到,如果您的应用中有AccessibilityService并且用户已启用该应用,那么您的应用会通过这些制造商应用的过滤器,应用会收到启动完成事件和其他任何内容{ {1}}按预期工作。

这个技巧的可能解释是,由于BroadcastReceiver是系统级服务,因此通过注册您自己的服务,您通过这些制造商应用的特定过滤器并尽快当您的自定义AccessibilityService被操作系统触发时,您的应用会在收到您已注册的符合条件的AccessibilityService时变为有效状态。

所以,这是怎么做的,

首先将此权限添加到BroadcastReceiver

AndroidManifest.xml

这样您就可以在系统中注册应用的<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE"/>

现在,通过在项目的res文件夹下的XML文件夹中创建一个文件AccessibilityService,为AccessibilityService添加一个非常基本的配置。

my_accessibility_service.xml

还有一个步骤要做,在项目中定义自定义<?xml version="1.0" encoding="utf-8"?> <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:accessibilityFeedbackType="feedbackSpoken" android:description="@string/service_desc" android:notificationTimeout="100"/>

AccessibilityService

请注意,由于您不需要public class MyAccessibilityService extends AccessibilityService { @Override public void onAccessibilityEvent(AccessibilityEvent event) { } @Override public void onInterrupt() { } } 用于任何目的而不是此解决方法,因此您可以将重写的方法留空。

最后,只需在AccessibilityService

中声明AccessibilityService即可
AndroidManifest.xml

这就是全部。现在,在您的应用程序中,只需要求您的用户从设置中打开您的应用程序的辅助功能服务并保持打开状态!您的应用程序在所有设备上都能正常工作,即使操作系统在过滤时会自动启动应用程序的过滤器。

编辑1

以下是如何检查您的应用是否已启用辅助功能服务

<service
    android:name=".MyAccessibilityService"
    android:label="@string/app_name"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
    <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService"/>
    </intent-filter>

    <meta-data
        android:name="android.accessibilityservice"
        android:resource="@xml/my_accessibility_service"/>
</service>

希望这有帮助。

答案 1 :(得分:5)

嗨,我迟到了,但是我从这个问题开始就跟着这个问题了。我知道One-plus和其他一些OEM维护着一个可以接收BOOT_COMPLETED广播的应用列表。如果您的应用未列入白名单,那么您的应用将无法在启动时启动。现在我的解决方案在内存和资源方面非常高效,并且保证在重启或硬启动后启动任务或服务也不需要answer中提出的AccessibilityService。这就是..

  1. 添加<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> manifest文件中的权限。
  2. 2.如果您对com.google.android.gms:play-services-gcm没有依赖关系,请将以下内容添加到build.gradle的依赖项部分:

    compile 'com.firebase:firebase-jobdispatcher:0.5.2'
    

    否则添加以下内容:

    compile 'com.firebase:firebase-jobdispatcher-with-gcm-dep:0.5.2'
    

    这是来自firebase小组的library,它依赖于google-play-service库来安排您的工作,从我的角度来看,google-play-service有权在启动时启动而不是系统google-play-service将在设备重启后立即运行您的工作。

    1. 现在这一步很简单只需定义一个JobService类

      public class MyJobService extends JobService {
      
      @Override
      public boolean onStartJob(JobParameters job) {
          Log.v("Running", "====>>>>MyJobService");
      
          return false; // Answers the question: "Is there still work going on?"
      }
      
      @Override
      public boolean onStopJob(JobParameters job) {
          Log.v("Stopping", "====>>>>MyJobService");
          return true; // Answers the question: "Should this job be retried?"
      }
      
    2. }

      1. 在清单文件中添加您的作业服务。

        <service
          android:exported="false"
          android:name=".MyJobService">
        <intent-filter>
            <action android:name="com.firebase.jobdispatcher.ACTION_EXECUTE"/>
        </intent-filter>
        </service>
        
      2. 在您想要的任何地方安排此工作,例如,当您的应用开始时。

               FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(getApplicationContext()));
        
                Bundle myExtrasBundle = new Bundle();
        
                myExtrasBundle.putString("some_key", "some_value");
        
                Job myJob = dispatcher.newJobBuilder()
                        // the JobService that will be called
                        .setService(MyJobService.class)
                        // uniquely identifies the job
                        .setTag("my-unique-tag-test")
                        // repeat the job
                        .setRecurring(true)
                        // persist past a device reboot
                        .setLifetime(Lifetime.FOREVER)
                        // start between 0 and 60 seconds from now
                        .setTrigger(Trigger.executionWindow(0, 60))
                        // don't overwrite an existing job with the same tag
                        .setReplaceCurrent(false)
                        // retry with exponential backoff
                        .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
                        // constraints that need to be satisfied for the job to run
                        .setExtras(myExtrasBundle)
        
                        .build();
        
                dispatcher.mustSchedule(myJob);
        
      3. 6.就是这样!现在,您可以在设备启动时执行任务或服务,无论您是否在白名单中。

        有一点需要注意,Google Play服务必须安装在设备上,否则无法使用。

答案 2 :(得分:3)

@Aritra,试试这个

<receiver
            android:name=".mics.BootReceiver"
            android:enabled="true"
            android:exported="true" >
            <intent-filter android:priority="500" >
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

删除quickBoot意图过滤器并尝试运行它,根据我们只需要BootCompleted来实现它的文档。可能是在打扰这个。

还有一点需要注意:

不要完全依赖或测试Mi设备,因为他们拥有自己的操作系统会停止Android的基本功能,就像他们停止推送通知服务和后台服务只是为了优化电池使用。要在Mi设备上对此进行测试,请在安全应用中将您的应用标记为“自动启动”,然后尝试。

答案 3 :(得分:2)

IntentFilter的工作方式是每个<intent-filter></intent-filter>包含一种启动组件的方法。如果您有多种方法可以解决这个问题(例如,您希望在一个BroadcastReceiver中收听两个操作),那么每个操作都需要一个独立的<intent-filter></intent-filter>定义。

因此,您可以尝试更改:

<receiver android:name=".mics.BootReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
        <action android:name="android.intent.action.QUICKBOOT_POWERON" />
    </intent-filter>
</receiver>

为:

<receiver android:name=".mics.BootReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>

    <intent-filter>
        <action android:name="android.intent.action.QUICKBOOT_POWERON" />
    </intent-filter>
</receiver>

在此处阅读更多内容:Intents and Intent Filters | Android Developers

修改

如果它仍然不起作用,您可以尝试测试您的清单声明是否正确完成。尝试在终端中执行以下命令,保持测试设备连接到计算机:

adb shell am broadcast -a android.intent.action.BOOT_COMPLETED -n com.app.myapp/.mics.BootReceiver

如果这不起作用,您应该在清单文件中重新检查接收者的相对包声明。

编辑2

这可能听起来很奇怪,但请尝试按照以下步骤操作:

  • 从手机中卸载应用程序(确保为所有用户卸载)
  • 重新启动手机
  • 清理项目
  • 再次在您的设备中构建并运行项目

答案 4 :(得分:1)

近一年来,我一直在努力解决这个问题。在我所有的应用程序中,我都会向用户显示一条通知,以禁用我的应用程序的电池优化。

在One Plus设备上进行了大量测试之后,当我为应用关闭电池优化功能后,我能够收到启动完成的广播。我认为,它比上面讨论的可访问性服务好得多。

要求用户为应用程序禁用电池优化的最简单方法是显示某种通知,并在用户单击时打开电池优化页面。您可以使用下面的代码来做到这一点。

public void openPowerSettings(View v) {

    /* Make Sure to add below code to manifest
    <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
    */

    try {
        Intent i = new Intent(android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
        startActivityForResult(i, 1);
    }  catch (Exception e) {
        Log.e (TAG, "Exception: " + e.toString());
    }

}

如果下面的函数返回true,您也可以隐藏通知。

public static boolean is_ignoring_battery_optimizations(Context context) {
    String PACKAGE_NAME = context.getPackageName();
    PowerManager pm = (PowerManager) context.getSystemService(context.POWER_SERVICE);
    boolean status = true;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
        status = pm.isIgnoringBatteryOptimizations(PACKAGE_NAME);
    }
    return status;
}

答案 5 :(得分:0)

您可以要求用户提供自动启动权限,并将其定向到所需的设置页面:

 private void autoStart() {
    try {
        Intent intent = new Intent();
        String manufacturer = android.os.Build.MANUFACTURER;
        if ("xiaomi".equalsIgnoreCase(manufacturer)) {
            intent.setComponent(new ComponentName("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity"));
        } else if ("oppo".equalsIgnoreCase(manufacturer)) {
            intent.setComponent(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.permission.startup.StartupAppListActivity"));
        } else if ("vivo".equalsIgnoreCase(manufacturer)) {
            intent.setComponent(new ComponentName("com.vivo.permissionmanager", "com.vivo.permissionmanager.activity.BgStartUpManagerActivity"));
        } else if ("Letv".equalsIgnoreCase(manufacturer)) {
            intent.setComponent(new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.AutobootManageActivity"));
        } else if ("Honor".equalsIgnoreCase(manufacturer)) {
            intent.setComponent(new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity"));
        } else if ("oneplus".equalsIgnoreCase(manufacturer)) {
            intent.setComponent(new ComponentName("com.oneplus.security", "com.oneplus.security.chainlaunch.view.ChainLaunchAppListAct‌​ivity"));
        }

        List<ResolveInfo> list = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        if (list.size() > 0) {
            startActivity(intent);
        }

    } catch (Exception e) {
        Log.e("exc", String.valueOf(e));
    }
}

这样做之后,接收器总是在重新启动时触发。

答案 6 :(得分:-1)

如何在设备启动时启动服务(自动运行应用程序等)

首先:从Android 3.1+版本开始,如果用户从未启动过您的应用程序至少一次或用户“强制关闭”应用程序,则不会收到BOOT_COMPLETE。 这样做是为了防止恶意软件自动注册服务。此安全漏洞已在较新版本的Android中关闭。

解决方案:

使用活动创建应用。当用户运行它时,应用程序可以接收BOOT_COMPLETE广播消息。

第二个:在安装外部存储之前发送BOOT_COMPLETE。如果app安装到外部存储器,它将不会收到BOOT_COMPLETE广播消息。

在这种情况下,有两种解决方案:

  1. 将您的应用安装到内部存储
  2. 在内部存储中安装另一个小应用。此应用程序接收BOOT_COMPLETE并在外部存储上运行第二个应用程序。
  3. 如果您的应用已安装在内部存储中,则下面的代码可以帮助您了解如何在设备启动时启动服务。

    在Manifest.xml中

    许可:

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

    注册您的BOOT_COMPLETED接收器:

    <receiver android:name="org.yourapp.OnBoot">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED"/>
        </intent-filter>
    </receiver>
    

    注册您的服务:

    <service android:name="org.yourapp.YourCoolService" />
    

    在接收器OnBoot.java中:

    public class OnBoot extends BroadcastReceiver
    {
    
        @Override
        public void onReceive(Context context, Intent intent) 
        {
            // Create Intent
            Intent serviceIntent = new Intent(context, YourCoolService.class);
            // Start service
            context.startService(serviceIntent);
    
        }
    
     }
    

    对于HTC,如果设备没有捕获RECEIVE_BOOT_COMPLETED,你可能还需要添加Manifest这个代码:

    <action android:name="android.intent.action.QUICKBOOT_POWERON" />
    

    接收器现在看起来像这样:

    <receiver android:name="org.yourapp.OnBoot">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED"/>
            <action android:name="android.intent.action.QUICKBOOT_POWERON" />
        </intent-filter>
    </receiver>
    

    如何在没有重启模拟器或真实设备的情况下测试BOOT_COMPLETED? 这很简单。试试这个:

    adb -s device-or-emulator-id shell am broadcast -a android.intent.action.BOOT_COMPLETED
    

    如何获取设备ID?获取ID为的连接设备列表:

    adb devices
    
    默认情况下,您可以在ADT中找到

    adb:

    adt-installation-dir/sdk/platform-tools
    

    享受! )