在SDK内使用新的Android广告标识符

27

使用Android广告SDK使用Android的新广告商ID非常合理。

似乎只能通过使用Google服务SDK获取该ID,如此处所述:http://developer.android.com/google/play-services/id.html

使用Google Play服务SDK需要引用google-play-services_lib项目,这会导致几个问题:

  1. 很多SDK都是JAR文件,意味着它们无法直接使用google-play-services_lib(因为它们不能包含资源)。
  2. 如果我只想要广告商ID,则需要将google-play-services_lib添加到我的项目中,其大小接近1MB。

是否有一种方法可以仅获取广告商ID,而无需使用资源?

5个回答

45

我遇到了同样的问题,如果你只需要advertiserId,你可以直接使用Intent与Google Play服务交互。以下是自定义类的示例:

import java.io.IOException;
import java.util.concurrent.LinkedBlockingQueue;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Looper;
import android.os.Parcel;
import android.os.RemoteException;

public final class AdvertisingIdClient {

public static final class AdInfo {
    private final String advertisingId;
    private final boolean limitAdTrackingEnabled;

    AdInfo(String advertisingId, boolean limitAdTrackingEnabled) {
        this.advertisingId = advertisingId;
        this.limitAdTrackingEnabled = limitAdTrackingEnabled;
    }

    public String getId() {
        return this.advertisingId;
    }

    public boolean isLimitAdTrackingEnabled() {
        return this.limitAdTrackingEnabled;
    }
}

public static AdInfo getAdvertisingIdInfo(Context context) throws Exception {
    if(Looper.myLooper() == Looper.getMainLooper()) throw new IllegalStateException("Cannot be called from the main thread");

    try { PackageManager pm = context.getPackageManager(); pm.getPackageInfo("com.android.vending", 0); }  
    catch (Exception e) { throw e; }

    AdvertisingConnection connection = new AdvertisingConnection();
    Intent intent = new Intent("com.google.android.gms.ads.identifier.service.START");
    intent.setPackage("com.google.android.gms");
    if(context.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
        try {
            AdvertisingInterface adInterface = new AdvertisingInterface(connection.getBinder());
            AdInfo adInfo = new AdInfo(adInterface.getId(), adInterface.isLimitAdTrackingEnabled(true));
            return adInfo;
        } catch (Exception exception) {
            throw exception;
        } finally {
            context.unbindService(connection);
        }
    }       
    throw new IOException("Google Play connection failed");     
}

private static final class AdvertisingConnection implements ServiceConnection {
    boolean retrieved = false;
    private final LinkedBlockingQueue<IBinder> queue = new LinkedBlockingQueue<IBinder>(1);

    public void onServiceConnected(ComponentName name, IBinder service) {
        try { this.queue.put(service); }
        catch (InterruptedException localInterruptedException){}
    }

    public void onServiceDisconnected(ComponentName name){}

    public IBinder getBinder() throws InterruptedException {
        if (this.retrieved) throw new IllegalStateException();
        this.retrieved = true;
        return (IBinder)this.queue.take();
    }
}

private static final class AdvertisingInterface implements IInterface {
    private IBinder binder;

    public AdvertisingInterface(IBinder pBinder) {
        binder = pBinder;
    }

    public IBinder asBinder() {
        return binder;
    }

    public String getId() throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        String id;
        try {
            data.writeInterfaceToken("com.google.android.gms.ads.identifier.internal.IAdvertisingIdService");
            binder.transact(1, data, reply, 0);
            reply.readException();
            id = reply.readString();
        } finally {
            reply.recycle();
            data.recycle();
        }
        return id;
    }

    public boolean isLimitAdTrackingEnabled(boolean paramBoolean) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        boolean limitAdTracking;
        try {
            data.writeInterfaceToken("com.google.android.gms.ads.identifier.internal.IAdvertisingIdService");
            data.writeInt(paramBoolean ? 1 : 0);
            binder.transact(2, data, reply, 0);
            reply.readException();
            limitAdTracking = 0 != reply.readInt();
        } finally {
            reply.recycle();
            data.recycle();
        }
        return limitAdTracking;
    }
}
}

请确保您不是从主UI线程调用此函数。例如,使用以下方法:

new Thread(new Runnable() {        
    public void run() {
        try {
            AdInfo adInfo = AdvertisingIdClient.getAdvertisingIdInfo(context);
            advertisingId = adInfo.getId();
            optOutEnabled = adInfo.isLimitAdTrackingEnabled();
        } catch (Exception e) {
            e.printStackTrace();                            
        }                       
    }
}).start();

1
这个使用起来感觉很危险。类似这样的东西有文档记录吗? - Rohit Sharma
我在com.google.android.gms.ads.identifier.AdvertisingIdClient.getAdvertisingIdInfo遇到了以下错误: java.lang.NoSuchFieldError:android.content.pm.PackageInfo.signatures我该如何避免这个错误? - mdavid
工作示例在此处:https://github.com/facebook/facebook-android-sdk/blob/d1d8d06bfdd70b7a925388ea9385f6319141c9d5/facebook-core/src/main/java/com/facebook/internal/AttributionIdentifiers.java - Lisitso

9

Adrian的解决方案非常好,我自己也使用它。

然而,今天我发现当设备上没有安装Google Play服务时,它有一个漏洞。 当您的活动/服务停止时,您将收到有关泄漏 ServiceConnection 的消息。 实际上,这是Context.bindService中的一个错误:当绑定到服务失败时(在这种情况下是因为未安装Google Play服务),Context.bindService返回false,但它不会清除对ServiceConnection的引用,并期望您调用Context.unbindService即使服务不存在!

解决方法是更改getAdvertisingIdInfo的代码如下:

public static AdInfo getAdvertisingIdInfo(Context context) throws Exception {
    if(Looper.myLooper() == Looper.getMainLooper())
        throw new IllegalStateException("Cannot be called from the main thread");

    try {
        PackageManager pm = context.getPackageManager();
        pm.getPackageInfo("com.android.vending", 0);
    } catch(Exception e) {
        throw e;
    }

    AdvertisingConnection connection = new AdvertisingConnection();
    Intent intent = new Intent("com.google.android.gms.ads.identifier.service.START");
    intent.setPackage("com.google.android.gms");
    try {
        if(context.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
            AdvertisingInterface adInterface = new AdvertisingInterface(connection.getBinder());
            AdInfo adInfo = new AdInfo(adInterface.getId(), adInterface.isLimitAdTrackingEnabled(true));
            return adInfo;
        }
    } catch(Exception exception) {
        throw exception;
    } finally {
        context.unbindService(connection);
    }
    throw new IOException("Google Play connection failed");
}

那么即使 Context.bindService 返回 falseContext.unbindService 仍将被调用。

6
注意:我的答案已经过时,因为现在你可以选择要在项目中包含GooglePlayServices库的哪些部分。
最近我遇到了同样的问题,当我正在处理的项目达到65k dex限制时。
以下是我解决问题的方法:
1.前往https://code.google.com/p/jarjar/downloads/list并下载最新的Jar jar Links(以.jar格式)。将文件放在工作文件夹中。对于本示例,我将使用桌面。
2.转到[Android SDK Path] \ extras \ google \ google_play_services \ libproject \ google-play-services_lib \ libs,并将google-play-services.jar复制到同一工作文件夹中。
3.在同一文件夹中创建一个名为rules.txt的文本文件(名称并不重要)。
4.在rules.txt中粘贴文本(不带引号): "keep com.google.android.gms.ads.identifier.AdvertisingIdClient"
  • 如果您想保留其他类,可以在此处添加它们。

  • 打开命令提示符文件并将路径更改为您的工作文件夹。在Windows上使用[cd]命令。

  • 编写以下命令:

java -jar [jarjar归档] process [rulesFile] [inJar] [outJar]

java -jar jarjar-1.4.jar process rules.txt google-play-services.jar google-play-services-lite.jar

  • 执行该命令。

它的作用是什么:

  • 该命令将在工作文件夹中生成一个新的Java归档文件(*.jar),其中只包含获取广告商ID及其依赖项所需的类。因此,google-play-services.jar的大小将从2.2 Mb减少到约50kb

如何使用:

  • 像往常一样从sdk导入Google Play服务到您的项目中,确保将其复制到您的工作区。在libs文件夹中,用先前生成的jar替换google-play-services.jar。

  • 如果可能,您还可以删除资源以释放另外0.5 MB。请确保保留values/common_strings.xml和values/version.xml。

  • 不要忘记为Google Play服务添加清单元数据。

这帮助我将项目大小缩小超过2.5 MB,并保持在65k dex类和方法限制以下,同时能够访问Google广告商ID。

希望它也能帮到您。


1

MoPub和其他几家大公司没有把GPS纳入其SDK中。以下是MoPub帮助页面上的内容:

MoPub SDK不需要Google Play服务。如果您已经安装了它,我们将自动使用新的Google广告ID。如果您没有安装Google Play服务,我们将继续传递旧版Android ID。请注意,所有发布者都需要在8月1日之前在其应用中使用GPS,以防止其应用被Google Play商店拒绝。

查看此链接获取更多详细信息:

http://help.mopub.com/customer/portal/articles/1523610-google-advertising-id-faqs

希望这有所帮助。

0

访问广告 ID 的唯一支持方法是通过直接链接到 Play Services SDK 并通过这些 API 访问广告 ID。Google 不建议或支持任何避免直接访问 Play Services API 的解决方法,因为它会破坏用户面向功能(例如在设备上的 Play Services 应用程序过时的情况下的错误处理),并且其行为将与未来的 Play Services 发布不可预测。

Google Play 开发者计划政策 要求您仅以授权方式访问 Google Play API。


谢谢Eric,这很有用。然而,你的回答并没有提供解决@dors和我们大多数人遇到的两个问题的方案。请告诉我们是否有更好的方法。 - Adrian
Facebook在其SDK中使用它:https://github.com/facebook/facebook-android-sdk/blob/d1d8d06bfdd70b7a925388ea9385f6319141c9d5/facebook-core/src/main/java/com/facebook/internal/AttributionIdentifiers.java - Lisitso

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