Android 5.0 (L) 中的Google分析需要明确指定服务意图。

68

我的代码在 <5 版本上可以正常工作,但在 Android 5.0 上遇到了一个我不太理解的问题。

10-23 10:18:18.945: E/AndroidRuntime(8987): java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.google.android.gms.analytics.service.START (has extras) }

我的代码现在甚至可以在4.4.4及以下版本上运行。那我需要做什么呢?我会在下面发布相关的代码。同时,在我的谷歌搜索中,我发现了这篇有关于Android 5.0的 java.lang.IllegalArgumentException: Service Intent must be explicit 的文章,但我不明白它的意思。

清单文件

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="xxxxx.android.phone.xxxxx"
    android:versionCode="3"
    android:versionName="v1.2.4065" >

    <uses-sdk android:minSdkVersion="12"
        android:targetSdkVersion="21" />

    <!-- Required for Google Analytics -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <!-- For push notifications (GCM) -->
    <permission android:name="xxxxx.android.phone.xxxxx.permission.C2D_MESSAGE" android:protectionLevel="signature" />
    <uses-permission android:name="xxxxx.android.phone.xxxxx.permission.C2D_MESSAGE" />
    <!-- App receives GCM messages. -->
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <!-- GCM connects to Google Services. -->
    <uses-permission android:name="android.permission.INTERNET" /> 
    <!-- GCM requires a Google account. -->
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <!-- Keeps the processor from sleeping when a message is received. -->
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <!-- GCM - We handle notifications differently if the app is running -->
    <uses-permission android:name="android.permission.GET_TASKS" /> 

    <!-- Caching -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <!-- The event subscribe button adds events to the calendar -->
<!--    <uses-permission android:name="android.permission.WRITE_CALENDAR" /> -->
<!--    <uses-permission android:name="android.permission.READ_CALENDAR" />  -->

    <supports-screens
        android:resizeable="true"
        android:smallScreens="false"
        android:normalScreens="true"
        android:largeScreens="true"
        android:xlargeScreens="true"
        android:anyDensity="true" />

    <application
        android:name="xxxxx.xxxxxApplication"
        android:icon="@drawable/app_icon"
        android:label="@string/app_name"
        android:allowBackup="true"
        android:largeHeap="true" >
        <receiver 
            android:name="com.google.android.gcm.GCMBroadcastReceiver" 
            android:permission="com.google.android.c2dm.permission.SEND" >
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <category android:name="xxxxx.android.phone.xxxxx" />
            </intent-filter>
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
                <category android:name="xxxxx.android.phone.xxxxx" />
            </intent-filter>
        </receiver>

        <receiver 
            android:name="xxxxx.ConnectivityReceiver"
            android:enabled="false" >
            <intent-filter>
                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
            </intent-filter>
        </receiver>

        <activity 
            android:name=".SplashActivity"
            android:configChanges="locale|orientation" 
            android:theme="@style/Theme.Splash"
            android:screenOrientation="portrait"
            android:noHistory="true" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:label="@string/app_name"
            android:theme="@style/Theme"
            android:windowSoftInputMode="adjustPan|stateVisible"
            android:name=".LoginActivity"
            android:configChanges="locale|orientation|screenSize" 
            android:screenOrientation="portrait" >
        </activity>
        <activity 
            android:name=".MainActivity" 
            android:theme="@style/Theme"
            android:configChanges="locale|orientation|screenSize" 
            android:screenOrientation="portrait"
            android:windowSoftInputMode="adjustPan|stateVisible" />

        <activity 
            android:name=".CountryPickerActivity" 
            android:theme="@style/Theme.Floating"
            android:configChanges="locale|orientation|screenSize" 
            android:screenOrientation="portrait"
            android:windowSoftInputMode="adjustPan|stateVisible" />
        <activity 
            android:name=".EventPickerActivity" 
            android:theme="@style/Theme.Floating"
            android:configChanges="locale|orientation|screenSize" 
            android:screenOrientation="portrait"
            android:windowSoftInputMode="adjustPan|stateVisible" />
        <activity 
            android:name=".TutorialActivity"
            android:theme="@style/Theme.Transparent"
            android:configChanges="locale|orientation|screenSize"
            android:screenOrientation="portrait" />

        <activity 
            android:name=".VideoPlayerActivity" 
            android:theme="@style/Theme"
            android:configChanges="orientation|screenSize" />

        <service android:name=".GCMIntentService" android:enabled="true" />
        <meta-data android:name="com.crashlytics.ApiKey" android:value="xxxxxxxxxxxxxxxx"/>
    </application>

</manifest>

GCMIntentService.java

public class GCMIntentService extends GCMBaseIntentService {
private static final int ATTEMPTS_MAX = 3;

final static boolean USE_DEV = false;
final static String XXXXX = "https://xxxxx/api.php";
final static String XXXXX = "http://dev.xxxxx/api.php";
final static String SUBSCRIPTION_KEY = "xxxxxxxxxxxxxxx"; // unique per app

    public GCMIntentService() {
        super(xxxxxx.SENDER_ID);
        if(GCMIntentService.USE_DEV) {
            host = XXXXX;
        } else {
            host = XXXXX;
        }
    }

    ...

}

** 编辑 **

我越看这个问题,越觉得它不在 GCMIntentService.java 中。 我应该先发布我的堆栈跟踪:

10-23 13:17:08.095: E/AndroidRuntime(10560): FATAL EXCEPTION: GAThread
10-23 13:17:08.095: E/AndroidRuntime(10560): Process: xxxxx.android.phone.xxxxx, PID: 10560
10-23 13:17:08.095: E/AndroidRuntime(10560): java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.google.android.gms.analytics.service.START (has extras) }
10-23 13:17:08.095: E/AndroidRuntime(10560):    at android.app.ContextImpl.validateServiceIntent(ContextImpl.java:1674)
10-23 13:17:08.095: E/AndroidRuntime(10560):    at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1773)
10-23 13:17:08.095: E/AndroidRuntime(10560):    at android.app.ContextImpl.bindService(ContextImpl.java:1751)
10-23 13:17:08.095: E/AndroidRuntime(10560):    at android.content.ContextWrapper.bindService(ContextWrapper.java:538)
10-23 13:17:08.095: E/AndroidRuntime(10560):    at com.google.analytics.tracking.android.AnalyticsGmsCoreClient.connect(AnalyticsGmsCoreClient.java:82)
10-23 13:17:08.095: E/AndroidRuntime(10560):    at com.google.analytics.tracking.android.GAServiceProxy.connectToService(GAServiceProxy.java:279)
10-23 13:17:08.095: E/AndroidRuntime(10560):    at com.google.analytics.tracking.android.GAServiceProxy.createService(GAServiceProxy.java:163)
10-23 13:17:08.095: E/AndroidRuntime(10560):    at com.google.analytics.tracking.android.GAThread.init(GAThread.java:95)
10-23 13:17:08.095: E/AndroidRuntime(10560):    at com.google.analytics.tracking.android.GAThread.run(GAThread.java:493)

所以我会尝试将GA作为显式意图来运行。


很不幸,我对这个领域并不太了解,但是这里有一篇Reddit帖子,涉及许可证服务和显式意图;https://www.reddit.com/r/androiddev/comments/2jspqi/got_many_recent_reports_of_license_verification/。 - harism
11个回答

44

如果您正在尝试使用Google的许可机制,下面是对我有效的解决方案:

// explicit Intent, safe
Intent serviceIntent = new Intent(ILicensingService.class.getName());
serviceIntent.setPackage("com.android.vending");
boolean bindResult = mContext.bindService(serviceIntent, this, Context.BIND_AUTO_CREATE);

此文件位于com/google/android/vending/licensing/LicenseChecker.java。搜索 "Base64.decode("。

编辑:

添加对必须修补的Google许可Java文件的引用:

com.google.android.vending.licensing.LicenseChecker.checkAccess(LicenseChecker.java:150)

修补程序:

new String(
-    Base64.decode("Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U="))),
+    Base64.decode("Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U=")))
+    .setPackage("com.android.vending"), // this fix the 'IllegalArgumentException: Service Intent must be explicit'
     this, // ServiceConnection.

来源:https://code.google.com/p/android/issues/detail?id=78505#c19


我们需要在清单文件中为服务添加意图过滤器吗?那里应该填什么值? - Ashutosh Nigam
这应该是被接受的答案,因为它解决了Android Lollipop中的问题。 - IgorGanapolsky
我该如何找到上述的Java文件? - h_k
@h_k 我假设你已经从SDK extras导入了许可库项目,当你进行导入时,你将会拥有这个类(甚至可以编辑,但不要这么做)。 - milosmns
@Warpzit 我的意思是除了这个补丁中的几行代码之外 :) 否则,编辑可能会产生一些新问题,所以我猜 Google 应该更新他们的代码。 - milosmns
显示剩余2条评论

38

自Android 5.0(Lollipop)起,必须始终使用显式意图调用bindService()。 这以前是一项建议,但自从Lollipop以来,已经强制执行:每次使用隐式意图调用bindService()时都会抛出java.lang.IllegalArgumentException:Service Intent must be explicit。 显式意图和隐式意图的区别在于,后者通过名称指定要启动的组件(完全限定类名)。 请在此处查看有关意图类型的文档。

您遇到的问题是由于未升级到符合Android 5 Lollipop上绑定服务时隐式意图的限制的较新版本的google库所导致。 要解决此问题,如果可用,可以将库升级到较新版本,或者更新库代码并使用修改后的版本构建项目。

如果通常情况下没有合适的库升级,则需要修改源代码(在您的情况下为com.google.analytics.tracking.android.AnalyticsGmsCoreClient.connect()),在调用bindService()之前调用 intent.setPackage(packageName),其中 intent bindService()调用的第一个参数, packageName 是包含代码试图启动的服务的包的名称(在您的情况下为“com.google.android.gms.analytics”)。

您可以使用此代码作为示例:Unity更新的Google许可库(LVL)版本,调用显式意图的bindService,并且不会导致IllegalArgumentException。

解决问题的另一种方法是使用targetSDK不晚于19重新构建项目和google库。这将使其在Lollipop上无崩溃地运行,但这是较不安全的选项,并阻止您使用在后来版本中引入的SDK功能(适用于Android 5)。


以上的答案导致我的许可证检查失败,但Unity的解决方案起作用了。谢谢,+1。 - Marcus
我将targetSDK设置为18,但仍然遇到了这个问题。 - Yeung

21

将Google Analytics v2迁移至v3为我解决了问题。


3
嗨!我正在使用分析版v4,今天在棒棒糖LGE设备(Nexus 5)上遇到了这个异常。有什么建议吗?我创建了一个单独的文件来负责发送屏幕名称和事件。TrackerName是App_Tracker。 - Ankit Garg
1
我没有尝试使用v4,但也许你遇到了这个问题是因为其他服务的原因。尝试完全关闭它。 - nickkadrov
1
@AnkitGarg,听起来我们在谈论不同的软件,因为Google Analytics SDK仍然只有主要版本3。 - Jacksonkr
2
我正在使用版本v3,但在targetSdkVersion=21的Android 5.0设备上仍然遇到相同的错误。但是,在targetSdkVersion=19的Android 5.0设备上,我没有遇到这个错误。我不明白原因。我该怎么办? - realuser
@nickkadrov,我不明白这个答案如何解决Android Lollipop的问题。现在,只需在Intent上设置setPackageName即可解决此错误。 - IgorGanapolsky
显示剩余3条评论

14

我之前也遇到了这个问题。问题出在启动服务的活动中。

基本上,当启动服务时,显式意图直接在意图中命名服务。有关更详细的解释,请参见http://developer.android.com/guide/components/intents-filters.html

由于你没有发布活动代码,所以我不知道你现在是如何启动它的,但它应该看起来像这样:

Intent startIntent = new Intent(this, ServiceToStart.class);
this.startService(startIntent); // or bindService(...)

还不确定发生了什么。我的主要活动非常庞大,所以我应该只发布我的 onCreate 吗?那是 GA 服务启动的地方。 - Jacksonkr
1
内在意图是问题所在,但在GA(Google Analytics)库内部。我本来打算升级到新版本(3.0),所以我只是从2迁移到了3,然后就完成了。 - Jacksonkr
4
对我来说这解决了问题。我之前使用的是 new Intent(ISomeService.class.getName());,在4.4中可以正常工作但在5.0中不能。在将那一行更改为 new Intent(getApplicationContext(), ISomeService.class); 后,它又开始正常工作了。需要注意的是,即使在4.4中我已经收到了关于这个问题的警告,但我还是选择忽略了它,因为我只是编写一个可丢弃的测试类。 - Daniel
谢谢!这解决了我的问题,可能也节省了我许多时间去修复破损的代码! - SJTriggs
如果我在Activity的子类中初始化一个类的构造函数,而不是在服务上使用GA,会怎样呢? - SleepNot
显示剩余3条评论

8
我使用了这个,它效果很好。
 public static Intent createExplicitFromImplicitIntent(Context context, Intent implicitIntent) {
     //Retrieve all services that can match the given intent
     PackageManager pm = context.getPackageManager();
     List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);

     //Make sure only one match was found
       if (resolveInfo == null || resolveInfo.size() != 1) {
        return null;
       }

     //Get component info and create ComponentName
     ResolveInfo serviceInfo = resolveInfo.get(0);
     String packageName = serviceInfo.serviceInfo.packageName;
     String className = serviceInfo.serviceInfo.name;
     ComponentName component = new ComponentName(packageName, className);

     //Create a new intent. Use the old one for extras and such reuse
     Intent explicitIntent = new Intent(implicitIntent);

     //Set the component to be explicit
     explicitIntent.setComponent(component);

     return explicitIntent;
 }

4
我正在处理一个项目,我们希望允许用户使用旧设备。我想到了与Marias答案中提到的相同解决方案,但是我添加了一个条件语句来调用setPackage,因为它仅在API 4(冰激凌三明治== SDK 14)及以上版本中可用。如果开发版本低于此,我认为您不需要包含setPackage调用。
在函数com.google.android.vending.licensing.LicenseChecker.checkAccess(callback)中。
Intent serviceIntent = new Intent(new String(
Base64.decode("Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U=")));

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
    serviceIntent.setPackage("com.android.vending");
}

boolean bindResult =
    mContext.bindService(
        serviceIntent,
        this, // ServiceConnection.
        Context.BIND_AUTO_CREATE);

2

如果您想启动另一个应用程序中的服务,可以使用以下代码:

Intent serviceIntent = new Intent("action name for the service");
serviceIntent.setPackage("the PackageName for which the service in)");//the destination packageName
context.startService(serviceIntent);

1

这对我很有帮助。在使用Android SDK 21的棒棒糖版本中有效。

Intent intent = new Intent(this, Class.forName(ServiceClassName.class.getName()));
bindService(intent,serviceConnection, Service.BIND_AUTO_CREATE);

4
如果ServiceClassName在其他APK中怎么办? - Shrenik

1
对于使用PhoneGap/Cordova的用户而言,如果看到这个错误,那是因为半官方的GAPlugin使用了已废弃的Google Analytics v2库。khalidb91对其进行了更新至v3,但截至本文撰写时,该更新尚未合并至半官方插件中。请从他的分支中获取代码,并将其直接替换plugins/com.adobe.plugins.GAPlugin,这样就不会再出现崩溃问题了。感谢khalidb91!

https://github.com/khalidb91/GAPlugin


0
这对我有效:
Intent intent = new Intent(ACTION);
intent.setPackage(context.getPackageName());
context.startService(intent);

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