服务意图必须是显式的:Intent

84

我现在有一个应用程序,其中我通过广播接收器(MyStartupIntentReceiver)调用服务。广播接收器中的代码以便调用服务是:

public void onReceive(Context context, Intent intent) {
    Intent serviceIntent = new Intent();
    serviceIntent.setAction("com.duk3r.eortologio2.MyService");
    context.startService(serviceIntent);
}
在Android 5.0 Lollipop中,我遇到了以下错误(在之前的Android版本中,一切正常):

问题是在Android 5.0 Lollipop中,我会遇到以下错误(在之前的Android版本中,一切都运行良好):

Unable to start receiver com.duk3r.eortologio2.MyStartupIntentReceiver: java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.duk3r.eortologio2.MyService }
我需要做哪些更改才能将服务声明为明确的并正常启动? 尝试了其他类似主题中的一些答案,虽然我已经摆脱了错误消息,但服务仍无法启动。

这是你的应用程序中的服务吗? - tyczj
是的,“com.duk3r.eortologio2”这个包名是我的应用。 - duk3r
4
仅当您希望第三方应用程序与该组件通信时,才在组件上使用<intent-filter>。您似乎陷入了一个误解,认为您需要在所有地方都使用<intent-filter>--实际上,您很少需要使用<intent-filter>。显式Intent是指在Intent本身中指定要通信的组件,通常使用以Java 'Class'对象作为第二个参数的构造函数。对于应用程序内部的组件,您应该使用显式Intent,而不是隐式的Intent<intent-filter> - CommonsWare
5个回答

140

你在应用程序中对任何服务、活动等所做的任何意图,都应始终遵循此格式

Intent serviceIntent = new Intent(context,MyService.class);
context.startService(serviceIntent);
Intent bi = new Intent("com.android.vending.billing.InAppBillingService.BIND");
bi.setPackage("com.android.vending");

隐式意图(即您当前代码中的内容)被认为是安全风险。


1
如果我需要bindService怎么办? - IgorGanapolsky
3
@IgorGanapolsky,那么你需要手动设置该软件包,以使其变得明确。 - tyczj
如果您想动态获取包,请使用BuildConfig.APPLICATION_ID。 - Gibolt
非常好的答案。这确实消除了我的“Service Intent must be explicit”错误。但现在我得到了“您的Market版本可能已过期。您可以继续使用此应用程序,但无法进行购买。”如何解决这个问题? - WebViewer

34

设置您的packageName使其正常工作。

intent.setPackage(this.getPackageName());

6

将隐式意图转换为显式意图,然后启动服务。

        Intent implicitIntent = new Intent();
        implicitIntent.setAction("com.duk3r.eortologio2.MyService");
        Context context = getApplicationContext();
        Intent explicitIntent = convertImplicitIntentToExplicitIntent(implicitIntent, context);
        if(explicitIntent != null){
            context.startService(explicitIntent);
            }


    public static Intent convertImplicitIntentToExplicitIntent(Intent implicitIntent, Context context) {
            PackageManager pm = context.getPackageManager();
            List<ResolveInfo> resolveInfoList = pm.queryIntentServices(implicitIntent, 0);

            if (resolveInfoList == null || resolveInfoList.size() != 1) {
                return null;
            }
            ResolveInfo serviceInfo = resolveInfoList.get(0);
            ComponentName component = new ComponentName(serviceInfo.serviceInfo.packageName, serviceInfo.serviceInfo.name);
            Intent explicitIntent = new Intent(implicitIntent);
            explicitIntent.setComponent(component);
            return explicitIntent;
        }

0

试试这个。这对我有效。这里的MonitoringService是我的服务类。我有两个操作,指示服务停止或启动。我根据AIRPLANE_MODE_CHANGED从我的broadcast receiver发送该值。

@Override
public void onReceive(Context context, Intent intent) {   
    String action = intent.getAction();

    if(Intent.ACTION_AIRPLANE_MODE_CHANGED.equalsIgnoreCase(action)){
         boolean isOn = intent.getBooleanExtra("state", false);
         String serviceAction = isOn? MonitoringService.StopAction : MonitoringService.StartAction;
         Intent serviceIntent = new Intent(context, MonitoringService.class);
         serviceIntent.setAction(serviceAction);
         context.startService(serviceIntent);
    }
}

注意:我添加了以下代码来触发我的广播接收器,名称为:ManageLocationListenerReceiver

<receiver
     android:name=".ManageLocationListenerReceiver"
     android:enabled="true"
     android:exported="true">
     <intent-filter>
         <action android:name="android.intent.action.AIRPLANE_MODE" />
     </intent-filter>
</receiver>

-1

我改进了Shahidul的答案,以消除上下文依赖性:

public class ServiceUtils {
    public static void startService(String intentUri) {
        Intent implicitIntent = new Intent();
        implicitIntent.setAction(intentUri);
        Context context = SuperApplication.getContext();
        Intent explicitIntent = convertImplicitIntentToExplicitIntent(implicitIntent, context);
        if(explicitIntent != null){
            context.startService(explicitIntent);
        }
    }

    private static Intent convertImplicitIntentToExplicitIntent(Intent implicitIntent, Context context) {
        PackageManager pm = context.getPackageManager();
        List<ResolveInfo> resolveInfoList = pm.queryIntentServices(implicitIntent, 0);

        if (resolveInfoList == null || resolveInfoList.size() != 1) {
            return null;
        }
        ResolveInfo serviceInfo = resolveInfoList.get(0);
        ComponentName component = new ComponentName(serviceInfo.serviceInfo.packageName, serviceInfo.serviceInfo.name);
        Intent explicitIntent = new Intent(implicitIntent);
        explicitIntent.setComponent(component);
        return explicitIntent;
    }
}

在SuperApplication类内部:

public class SuperApplication extends Application {
    private static MyApp instance;

    public static SuperApplication getInstance() {
        return instance;
    }

    public static Context getContext(){
        return instance;
        // or return instance.getApplicationContext();
    }

    @Override
    public void onCreate() {
        instance = this;
        super.onCreate();
    }
}

在你的清单文件中:
<application
    android:name="com.example.app.SuperApplication "
    android:icon="@drawable/icon"
    android:label="@string/app_name"
    .......
    <activity
        ......

然后,只需调用:

ServiceUtils.startService("com.myservice");

1
调用全局单例类来获取上下文并不是一个好主意。相反,请公开这个依赖项。 - Better Shao
如果ServiceUtils在活动之外,您如何调用应用程序上下文?您能提供一些示例吗? - Ângelo Polotto
1
ServiceUtils是一个实用工具,因此不必依赖于具体的Application类,如SuperApplication。调用startService方法的调用者有责任提供上下文。为了方便起见,可以在应用程序中的任何地方注入要使用的Application。但正如您可能已经发现的那样,允许在“任何地方”调用该方法并不是一个好主意。我们最好创建仅具有单一(组)职责的类,并避免不必要的依赖关系。这样,它就可以进行单元测试,代码也更加清晰。 - Better Shao
谢谢你的回答。我不会删除那个答案,让其他人也能更多地了解这个问题。 - Ângelo Polotto
你可以利用扩展函数的好处来提供上下文,就像这样:fun Context.startService(s: String),它可以从任何上下文包装器(如活动或服务)中调用。 - undefined

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