Android 设备重启后广播接收器不工作
Broadcast Receiver Not Working After Device Reboot in Android
我已经检查了所有相关问题,但没有找到解决此问题的方法。所以这对我来说是一个全新的问题。
我有什么
我有一个 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 项其他活动,但为简单起见已将其删除。这是基本的引导接收器 class。我从这里开始服务。
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);
}
}
}
和 phone 呼叫接收器 class 看起来像这样(它也被简化了),
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
}
}
}
问题
当我安装应用程序并启动一次时,所有这些接收器都工作正常。但在我重新启动我的设备后,这些接收器根本无法工作。 BootCompleteReceiver
和 PhoneCallReceiver
都没有调用它们的 onReceive()
方法。
我的假设是这些接收器会在重新启动后自动注册,但它不起作用。我需要 BootCompleteReceiver
正常工作,以便我可以在我的应用程序中启动一项重要服务。
我的观察
我已经彻底测试过了。重新启动设备后,接收器在我的 Nexus 5X (Nougat)、Nexus 6P (Nougat)、YU Yuphoria (Lollipop) 中工作正常,但在我的 OnePlus 3 中却不行(牛轧糖)和小米 4i(棒棒糖).
同一个代码怎么能在少数设备上完美运行,而在其他设备上根本无法运行?我什么都没改。
我在这里做错了什么?我的应用严重依赖于这些广播并基于这些启动服务。任何帮助将不胜感激。
编辑 1
为了更好地理解问题,我刚刚创建了一个非常小的测试项目,其中只有一个 activity 以及完全相同的 BootCompleteReceiver
和 PhoneCallReceiver
.
但奇怪的是,这个项目在我的 OnePlus 3 上完美运行,而我的实际应用程序的接收器在重启后不起作用。我最初假设问题出在 OS 或设备中,但事实并非如此。
那么真正的问题在哪里呢?它是在我的应用程序中(但它在其他设备上运行良好)还是在 OS 和设备中(小型测试项目在相同的 OS 和相同的设备上运行良好)?
我真的很困惑。我需要一些专家的帮助。
编辑 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)
可能是因为我的设备没有root。我无法以任何方式发送此广播。
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 最近更新到牛轧糖,他们引入了类似于 Mi 设备的功能,可以防止某些应用程序在重启后自动启动。
禁用此功能后,我的应用程序在重新启动后开始接收广播。但这仍然不能解释两件事。
1) 我的小测试项目在 AutoLaunch 应用程序列表中自动列入白名单,这就是它按预期工作的原因。但这怎么可能呢?为什么 OS 认为这个小应用程序值得自动启动?
2) 有一些应用程序,如 LockDown Pro、500 Firepaper 在 AutoLaunch 应用程序屏幕中被列入黑名单,但它仍然在我的 OnePlus 3 和 Mi 4i 中重新启动后接收广播。现在怎么可能?是否有可能以编程方式允许我的应用程序在这些设备(OnePlus 和 Mi)中自动启动?
编辑 5
我已经尝试了@Rahul Chowdhury 提出的解决方案,它似乎真的很有效。添加无障碍服务后问题重新解决
但是如果用户在授予辅助功能权限后撤销了它,那么我有没有办法以编程方式检查辅助功能权限是否可用于我的应用程序?
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
编辑
如果还是不行,你可以试试你的manifest声明是否正确。尝试在您的终端中执行以下命令,同时保持测试设备与计算机的连接:
adb shell am broadcast -a android.intent.action.BOOT_COMPLETED -n com.app.myapp/.mics.BootReceiver
如果这不起作用,您应该重新检查清单文件中接收方的相关包声明。
编辑 2
这听起来可能很奇怪,但请尝试按照以下步骤操作:
- 从您的 phone 中卸载该应用程序(确保为所有用户卸载它)
- 重新启动您的 phone
- 清理项目
- 再次构建并运行您设备中的项目
@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 intent 过滤器并尝试 运行 它,根据文档我们只需要 BootCompleted 就可以实现它。可能是它打断了这个。
还有一点要注意:
不要完全依赖或测试 Mi 设备,因为它们有自己的 OS,这会停止 Android 的基本功能,比如它们会停止推送通知服务和后台服务只是为了优化电池使用。要在小米设备上进行测试,请在安全应用中将您的应用标记为 "AutoStart",然后尝试。
如何在设备启动时启动服务(自动运行应用等)
首先:从版本 Android 3.1+ 开始,如果用户从未至少启动过一次您的应用程序或用户 "force closed" 应用程序,您将不会收到 BOOT_COMPLETE。
这样做是为了防止恶意软件自动注册服务。此安全漏洞已在 Android.
的较新版本中关闭
解决方案:
使用 activity 创建应用程序。当用户 运行 一次应用程序可以接收 BOOT_COMPLETE 广播消息。
第二个:BOOT_COMPLETE 在安装外部存储之前发送。如果应用程序安装到外部存储,它将不会收到 BOOT_COMPLETE 广播消息。
这种情况有两种解决方法:
- 将您的应用程序安装到内部存储空间
- 在内部存储中安装另一个小应用程序。此应用在外部存储设备上接收 BOOT_COMPLETE 和 运行 第二个应用。
如果您的应用已安装在内部存储中,则以下代码可以帮助您了解如何在设备启动时启动服务。
在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:
,您可能还需要在清单中添加此代码
<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
尽情享受吧! )
这是一个在您提到的 OnePlus 和 Mi 设备上经过测试且有效的解决方案。
如您所说,OnePlus 和 Mi 设备上的 auto-start 预防功能可防止应用程序在boot complete 以提高整体设备的启动速度和电池性能。但是,有一种变通方法可以让您的应用即使在启用此功能的情况下也能正常工作。
我注意到,如果您的应用程序中有一个 AccessibilityService
并且它被用户打开,那么您的应用程序会通过这些制造商应用的过滤器并且该应用程序会收到它的启动完成事件和任何其他 BroadcastReceiver
按预期工作。
这个技巧的可能解释是,由于 AccessibilityService
是系统级服务,所以通过注册您自己的服务,您 通过了这些制造商应用的特定过滤器,一旦您的自定义 AccessibilityService
被 OS 触发,您的应用就会开始接收您注册的符合条件的 BroadcastReceiver
。
所以,这是如何做到的,
首先将此权限添加到您的 AndroidManifest.xml
、
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE"/>
这将允许您向系统注册您的应用 AccessibilityService
。
现在,通过在项目的 res 文件夹下的 XML 文件夹中创建一个文件,例如 my_accessibility_service.xml
,为您的 AccessibilityService
添加一个非常基本的配置。
<?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
用于任何目的而不需要此解决方法,因此您可以将覆盖的方法留空。
最后,只需在您的 AndroidManifest.xml
、
中声明您的 AccessibilityService
<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>
就是这样。现在在您的应用程序中,只需要求您的用户从设置中为您的应用程序打开辅助功能服务并保持打开状态,瞧!您的应用程序在所有设备上都能正常运行,即使 OS 对哪些应用程序应该 auto-start 在启动时设置了过滤器也是如此。
编辑 1
您可以通过以下方式检查您的应用是否开启了无障碍服务,
private static final int ACCESSIBILITY_ENABLED = 1;
public static boolean isAccessibilitySettingsOn(Context context) {
int accessibilityEnabled = 0;
final String service = context.getPackageName() + "/" + MyAccessibilityService.class.getCanonicalName();
try {
accessibilityEnabled = Settings.Secure.getInt(
context.getApplicationContext().getContentResolver(),
android.provider.Settings.Secure.ACCESSIBILITY_ENABLED);
} catch (Settings.SettingNotFoundException e) {
Log.e("AU", "Error finding setting, default accessibility to not found: "
+ e.getMessage());
}
TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');
if (accessibilityEnabled == ACCESSIBILITY_ENABLED) {
String settingValue = Settings.Secure.getString(
context.getApplicationContext().getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (settingValue != null) {
mStringColonSplitter.setString(settingValue);
while (mStringColonSplitter.hasNext()) {
String accessibilityService = mStringColonSplitter.next();
if (accessibilityService.equalsIgnoreCase(service)) {
return true;
}
}
}
}
return false;
}
希望对您有所帮助。
嗨,我来晚了,但我从一开始就在关注这个问题。我知道 One-plus
和其他一些 OEM 维护着一个可以接收 BOOT_COMPLETED 广播的应用列表。如果您的应用未列入白名单,则您的应用将不会在启动时启动。现在我有一个解决方案,它在内存和资源方面非常有效,并且保证在重启或硬启动后启动您的任务或服务也不需要 AccessibilityService
,如本 中所建议的。开始了..
在您的 manifest
文件中添加以下权限
2.If 您没有 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
将在设备重新启动后 运行 你的工作。
- 现在这一步很简单只要定义一个JobServiceclass
public class MyJobService 扩展了 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?"
}
}
在清单文件中添加您的工作服务。
将此作业安排在您想要的任何位置,例如当您的应用程序启动时。
FirebaseJobDispatcher 调度程序 =
新 FirebaseJobDispatcher(新 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);
6.That就这样了!!现在无论您是否在白名单中,您都可以在设备启动时执行您的任务或服务。
有一点要注意 Google Play Service 必须安装在设备上,否则将无法运行。
我已经为这个问题苦苦挣扎了将近一年。在我所有的应用程序中,我都会向用户显示一条通知,让他们禁用我的应用程序的电池优化。
在 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());
}
}
如果下面的函数 returns 为真,您也可以隐藏通知。
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;
}
您可以向用户请求自动启动权限,并将他们引导至所需的设置页面:
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.ChainLaunchAppListActivity"));
}
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));
}
}
执行此操作后,接收器总是在重新启动时被触发。
我已经检查了所有相关问题,但没有找到解决此问题的方法。所以这对我来说是一个全新的问题。
我有什么
我有一个 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 项其他活动,但为简单起见已将其删除。这是基本的引导接收器 class。我从这里开始服务。
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);
}
}
}
和 phone 呼叫接收器 class 看起来像这样(它也被简化了),
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
}
}
}
问题
当我安装应用程序并启动一次时,所有这些接收器都工作正常。但在我重新启动我的设备后,这些接收器根本无法工作。 BootCompleteReceiver
和 PhoneCallReceiver
都没有调用它们的 onReceive()
方法。
我的假设是这些接收器会在重新启动后自动注册,但它不起作用。我需要 BootCompleteReceiver
正常工作,以便我可以在我的应用程序中启动一项重要服务。
我的观察
我已经彻底测试过了。重新启动设备后,接收器在我的 Nexus 5X (Nougat)、Nexus 6P (Nougat)、YU Yuphoria (Lollipop) 中工作正常,但在我的 OnePlus 3 中却不行(牛轧糖)和小米 4i(棒棒糖).
同一个代码怎么能在少数设备上完美运行,而在其他设备上根本无法运行?我什么都没改。
我在这里做错了什么?我的应用严重依赖于这些广播并基于这些启动服务。任何帮助将不胜感激。
编辑 1
为了更好地理解问题,我刚刚创建了一个非常小的测试项目,其中只有一个 activity 以及完全相同的 BootCompleteReceiver
和 PhoneCallReceiver
.
但奇怪的是,这个项目在我的 OnePlus 3 上完美运行,而我的实际应用程序的接收器在重启后不起作用。我最初假设问题出在 OS 或设备中,但事实并非如此。
那么真正的问题在哪里呢?它是在我的应用程序中(但它在其他设备上运行良好)还是在 OS 和设备中(小型测试项目在相同的 OS 和相同的设备上运行良好)?
我真的很困惑。我需要一些专家的帮助。
编辑 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)
可能是因为我的设备没有root。我无法以任何方式发送此广播。
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 最近更新到牛轧糖,他们引入了类似于 Mi 设备的功能,可以防止某些应用程序在重启后自动启动。
禁用此功能后,我的应用程序在重新启动后开始接收广播。但这仍然不能解释两件事。
1) 我的小测试项目在 AutoLaunch 应用程序列表中自动列入白名单,这就是它按预期工作的原因。但这怎么可能呢?为什么 OS 认为这个小应用程序值得自动启动?
2) 有一些应用程序,如 LockDown Pro、500 Firepaper 在 AutoLaunch 应用程序屏幕中被列入黑名单,但它仍然在我的 OnePlus 3 和 Mi 4i 中重新启动后接收广播。现在怎么可能?是否有可能以编程方式允许我的应用程序在这些设备(OnePlus 和 Mi)中自动启动?
编辑 5
我已经尝试了@Rahul Chowdhury 提出的解决方案,它似乎真的很有效。添加无障碍服务后问题重新解决
但是如果用户在授予辅助功能权限后撤销了它,那么我有没有办法以编程方式检查辅助功能权限是否可用于我的应用程序?
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
编辑
如果还是不行,你可以试试你的manifest声明是否正确。尝试在您的终端中执行以下命令,同时保持测试设备与计算机的连接:
adb shell am broadcast -a android.intent.action.BOOT_COMPLETED -n com.app.myapp/.mics.BootReceiver
如果这不起作用,您应该重新检查清单文件中接收方的相关包声明。
编辑 2
这听起来可能很奇怪,但请尝试按照以下步骤操作:
- 从您的 phone 中卸载该应用程序(确保为所有用户卸载它)
- 重新启动您的 phone
- 清理项目
- 再次构建并运行您设备中的项目
@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 intent 过滤器并尝试 运行 它,根据文档我们只需要 BootCompleted 就可以实现它。可能是它打断了这个。
还有一点要注意:
不要完全依赖或测试 Mi 设备,因为它们有自己的 OS,这会停止 Android 的基本功能,比如它们会停止推送通知服务和后台服务只是为了优化电池使用。要在小米设备上进行测试,请在安全应用中将您的应用标记为 "AutoStart",然后尝试。
如何在设备启动时启动服务(自动运行应用等)
首先:从版本 Android 3.1+ 开始,如果用户从未至少启动过一次您的应用程序或用户 "force closed" 应用程序,您将不会收到 BOOT_COMPLETE。 这样做是为了防止恶意软件自动注册服务。此安全漏洞已在 Android.
的较新版本中关闭解决方案:
使用 activity 创建应用程序。当用户 运行 一次应用程序可以接收 BOOT_COMPLETE 广播消息。
第二个:BOOT_COMPLETE 在安装外部存储之前发送。如果应用程序安装到外部存储,它将不会收到 BOOT_COMPLETE 广播消息。
这种情况有两种解决方法:
- 将您的应用程序安装到内部存储空间
- 在内部存储中安装另一个小应用程序。此应用在外部存储设备上接收 BOOT_COMPLETE 和 运行 第二个应用。
如果您的应用已安装在内部存储中,则以下代码可以帮助您了解如何在设备启动时启动服务。
在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:
,您可能还需要在清单中添加此代码<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
尽情享受吧! )
这是一个在您提到的 OnePlus 和 Mi 设备上经过测试且有效的解决方案。
如您所说,OnePlus 和 Mi 设备上的 auto-start 预防功能可防止应用程序在boot complete 以提高整体设备的启动速度和电池性能。但是,有一种变通方法可以让您的应用即使在启用此功能的情况下也能正常工作。
我注意到,如果您的应用程序中有一个 AccessibilityService
并且它被用户打开,那么您的应用程序会通过这些制造商应用的过滤器并且该应用程序会收到它的启动完成事件和任何其他 BroadcastReceiver
按预期工作。
这个技巧的可能解释是,由于 AccessibilityService
是系统级服务,所以通过注册您自己的服务,您 通过了这些制造商应用的特定过滤器,一旦您的自定义 AccessibilityService
被 OS 触发,您的应用就会开始接收您注册的符合条件的 BroadcastReceiver
。
所以,这是如何做到的,
首先将此权限添加到您的 AndroidManifest.xml
、
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE"/>
这将允许您向系统注册您的应用 AccessibilityService
。
现在,通过在项目的 res 文件夹下的 XML 文件夹中创建一个文件,例如 my_accessibility_service.xml
,为您的 AccessibilityService
添加一个非常基本的配置。
<?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
用于任何目的而不需要此解决方法,因此您可以将覆盖的方法留空。
最后,只需在您的 AndroidManifest.xml
、
AccessibilityService
<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>
就是这样。现在在您的应用程序中,只需要求您的用户从设置中为您的应用程序打开辅助功能服务并保持打开状态,瞧!您的应用程序在所有设备上都能正常运行,即使 OS 对哪些应用程序应该 auto-start 在启动时设置了过滤器也是如此。
编辑 1
您可以通过以下方式检查您的应用是否开启了无障碍服务,
private static final int ACCESSIBILITY_ENABLED = 1;
public static boolean isAccessibilitySettingsOn(Context context) {
int accessibilityEnabled = 0;
final String service = context.getPackageName() + "/" + MyAccessibilityService.class.getCanonicalName();
try {
accessibilityEnabled = Settings.Secure.getInt(
context.getApplicationContext().getContentResolver(),
android.provider.Settings.Secure.ACCESSIBILITY_ENABLED);
} catch (Settings.SettingNotFoundException e) {
Log.e("AU", "Error finding setting, default accessibility to not found: "
+ e.getMessage());
}
TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');
if (accessibilityEnabled == ACCESSIBILITY_ENABLED) {
String settingValue = Settings.Secure.getString(
context.getApplicationContext().getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (settingValue != null) {
mStringColonSplitter.setString(settingValue);
while (mStringColonSplitter.hasNext()) {
String accessibilityService = mStringColonSplitter.next();
if (accessibilityService.equalsIgnoreCase(service)) {
return true;
}
}
}
}
return false;
}
希望对您有所帮助。
嗨,我来晚了,但我从一开始就在关注这个问题。我知道 One-plus
和其他一些 OEM 维护着一个可以接收 BOOT_COMPLETED 广播的应用列表。如果您的应用未列入白名单,则您的应用将不会在启动时启动。现在我有一个解决方案,它在内存和资源方面非常有效,并且保证在重启或硬启动后启动您的任务或服务也不需要 AccessibilityService
,如本
在您的
manifest
文件中添加以下权限
2.If 您没有 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
将在设备重新启动后 运行 你的工作。
- 现在这一步很简单只要定义一个JobServiceclass
public class MyJobService 扩展了 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?"
}
}
在清单文件中添加您的工作服务。
将此作业安排在您想要的任何位置,例如当您的应用程序启动时。
FirebaseJobDispatcher 调度程序 = 新 FirebaseJobDispatcher(新 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);
6.That就这样了!!现在无论您是否在白名单中,您都可以在设备启动时执行您的任务或服务。
有一点要注意 Google Play Service 必须安装在设备上,否则将无法运行。
我已经为这个问题苦苦挣扎了将近一年。在我所有的应用程序中,我都会向用户显示一条通知,让他们禁用我的应用程序的电池优化。
在 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());
}
}
如果下面的函数 returns 为真,您也可以隐藏通知。
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;
}
您可以向用户请求自动启动权限,并将他们引导至所需的设置页面:
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.ChainLaunchAppListActivity"));
}
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));
}
}
执行此操作后,接收器总是在重新启动时被触发。