在进行 API 对接之前,需要您准备开户资料,并提供给商务经理,以获取您的投放链接 。
创建开发者账号,需要提供开发者名称,开发者任意一款APP名称(AppName)+包名(PackageName),平台(Android/IOS)和邮箱给商务经理,开户成功之后商务经理会把账户信息和投放链接、appk,和placement Id发给您。
开发者成功获取投放链接URL。
打开链接之前请将链接中的宏参数进行替换,如{did}替换为设备的GAID。 宏参数说明见以下表格。
投放链接支持浏览器和WebView两种打开方式,具体采用哪种方式根据各自的应用场景选择。
以这个投放链接为例:
https://h5.gleetap.com/interact/tml?appk={appk}&did={did}&pid={pid}| 参数 | 示例 | 必填 | 说明 |
|---|---|---|---|
| {appk} | ix50FctD24hyTozY | 是 | 广告平台分配的APP唯一标识符 |
| {did} | d2aa1dc2-b7ab-4c65-b2c2-b8572a116a5e | 是 | 开发者获取的设备广告ID《设备广告ID获取方法和示例》 |
| {pid} | 1 | 是 | 广告平台分配的唯一广告位ID |
由于TenAd上游广告的丰富多样,WebView本身需要对一些协议跳转协议进行支持,否则会出现跳转广告失败的问题。除此之外,还需要对用户的后退行为做一些响应,防止误操作退出WebView。
开发者如果确定了采用浏览器方式对接,正确替换投放URL的宏之后直接用浏览器打开即可。同时把对接方式同步给您的商务经理,因为我们是默认采用webview的对接方式。
开发者如果采用的是webview的方式,需要配合完成以下四步,Java对象注入、Java对象的方法实现、Url协议头、参数的支持和WebView后退支持。
WebView提供了一个addJavascriptInterface方法,将android原生定义的Java对象注入到JavaScript环境中。注入Java对象之后H5可以通过注入Java对象的名称调用Java对象中的方法来完成H5与原生的交互,注入的Java对象名称TenAdInterface开发者可以自定义。
//java对象注入WebJsBridge jsBridge = new WebJsBridge(webView.getUrl());webView.addJavascriptInterface(jsBridge, "TenAdInterface");//jsBridge,是注入JavaScript环境的Java对象。
TenAdInterface,是注入的Java对象名称。
⚠️注意⚠️
注入的java对象名称需要提供给与您对接的商务经理,如果提供给的名称与实际注入的名称不一致会导致集成的广告异常,例如广告无法正常跳转。
由于广告页面中存在需要外跳浏览器,打开新的广告页面和广告页面的关闭场景,所以需要在注入的原生Java对象中提以下三个方法给H5调用,外跳浏览器方法、打开广告页方法和关闭广告页方法。需要Java对象提供原生方法给H5调用,是因为有些场景H5页面内无法完成需要通过调用原生的方法协助完成:
1)、由于有些场景在广告页面内无法完成需要外跳到浏览器来完成,例如跳转到下载页或者应用市场完成应用下载。
2)、由于有些广告场景需要打开一个新的广告页面但由于跨域或其它问题导致无法在当前页面打开时,就需要新开一个WebView来打开。
外跳浏览器方法
外跳浏览器是原生Java对象提供的方法,H5可以通过注入的对象名称TenAdInterface调用openBrowser来完成浏览器的外跳。
public void openBrowser(String url) { Context context = GlobalUtil.getAppContext(); WebViewUtil.openBrowser(context, url); //参见WebViewUtil示例代码的外跳浏览器部分}打开广告页方法
打开新的广告页面是原生Java对象提供的方法,H5可以通过注入的对象名称TenAdInterface调用openWebview方法打开一个新的广告页面。
public void openWebview(String url) { //TODO open a new page to display landingpage.}
关闭广告页方法
广告页面会根据场景需要决定是否由H5提供一个关闭按钮来关闭广告页面,当点击关闭按钮时,H5需要通过注入的对象名称TenAdInterface调用close方法关闭广告页面。
public void close() { //TODO close the ad page}由于广告种类的多样性,为了兼容一些Url协议头和参数方案的广告,需要WebView对Url协议头和url参数的处理支持。包括 market链接、apk下载,其它协议头和外跳浏览器参数的支持。
webView.setWebViewClient(new WebViewClient() { public boolean shouldOverrideUrlLoading( WebView view, WebResourceRequest request) { Log.d(TAG, "shouldOverrideUrlLoading, " + request.getUrl()); if (request.getUrl() != null) { try { //参见WebViewUtil示例代码的WebView对url协议头和url参数的支持部分 if(WebViewUtil.openIntent(GlobalUtil.getMainActivity(), request.getUrl().toString())){ return true; } } catch (Throwable e) { return true; } } return super.shouldOverrideUrlLoading(view, request); }});// apk下载处理webView.setDownloadListener((url, userAgent, contentDisposition, mimeType, contentLength) -> { Uri uri = Uri.parse(url); Intent intent = new Intent(Intent.ACTION_VIEW, uri); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); GlobalUtil.getMainActivity().startActivity(intent);});WebViewUtil示例代码的WebView对url协议头和url参数的支持部分
为了防止误操作退出WebView,请支持网页的后退而不是直接关闭页面,参考示例代码
public void onBackPressed() { if (adWebView != null && adWebView.canGoBack()) { adWebView.goBack(); return; } super.onBackPressed();}WebView对http链接的支持
AndroidManifest.xml中application节点添加配置
android:usesCleartextTraffic="true"
如明确不支持http,请与你的商务经理说明。
xxxxxxxxxx<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" ......> <queries> <!-- 声明允许查询浏览器类应用, android11及以上的版本--> <intent> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="https" /> </intent> </queries></manifest> xxxxxxxxxxpublic class WebViewUtil { private static final String TAG = "WebViewUtil"; /** * 外跳浏览器方法 * @param context * @param url */ public static void openBrowser(Context context, String url) { if (isAppInstalled(context, "com.android.chrome")) { // 创建一个 Intent,指定 ACTION_VIEW 动作和 URL Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 指定要使用的浏览器的包名 intent.setPackage("com.android.chrome"); // Chrome 浏览器的包名 // 启动 Chrome 浏览器来处理链接 context.startActivity(intent); } else { // 如果没有安装 Chrome 浏览器,使用系统默认浏览器打开链接 Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 启动默认浏览器来处理链接 context.startActivity(intent); } }
private static final String kGpHttpScheme = "http://play.google.com/store/"; private static final String kGpHttpsScheme = "https://play.google.com/store/"; private static final String kMarketScheme = "market:"; private static final String kGpMarketIdUrl = "market://details?id="; private static final String kGpMarketUrl = "market://details"; private static final String kGooglePlayUrl = "https://play.google.com/store/apps/details"; /** * WebView对url协议头和外跳浏览器标识参数的支持 * @param context * @param url * @return */ public static boolean openIntent(Context context, String url) { if (url.startsWith(kMarketScheme) || url.startsWith(kGpHttpsScheme) || url.startsWith(kGpHttpScheme)) { // market链接 Intent intent = new Intent(Intent.ACTION_VIEW); if (url.startsWith(kGpMarketIdUrl)) { final String replace = url.replace(kGpMarketUrl, kGooglePlayUrl); Log.d(TAG, "marketUrl replace: \n" + url + "\n" + replace); intent.setData(Uri.parse(replace)); } else { intent.setData(Uri.parse(url)); } intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); return true; } else if (!url.startsWith("http://") && !url.startsWith("https://")) { //其它协议链接打开 openUrl(context, url); return true; } else if (url.contains("lz_open_browser=1")) { //lz_open_browser=1参数是外跳浏览器的标识 openBrowser(context, url); return true; } return false; }
/** * 检查包是否安装 * @param context * @param packageName * @return */ private static boolean isAppInstalled(Context context, String packageName) { if (TextUtils.isEmpty(packageName)) { return false; } try { context.getPackageManager().getPackageInfo(packageName, 0); return true; } catch (PackageManager.NameNotFoundException e) { return false; } }
/** * 其它协议链接打开 * @param context * @param url */ private static void openUrl(Context context, String url) { Intent intent = new Intent(Intent.ACTION_VIEW); ActivityInfo activityInfo = intent.resolveActivityInfo(context.getPackageManager(), 0); if (activityInfo.exported) { intent.setData(Uri.parse(url)); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } }}
xxxxxxxxxxdependencies { // ....... implementation "com.google.android.gms:play-services-ads-identifier:18.0.1"}xxxxxxxxxx<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" ....> <uses-permission android:name="com.google.android.gms.permission.AD_ID"/></manifest> x//获取广告设备ID时,注意不能在UI线程调用AdvertisingIdClient.getGaid(MainActivity.this, gaid -> { //gaid});
public class AdvertisingIdClient { private static final String TAG = "AdvertisingIdClient"; public interface OnGetGaidListener { void onGetGaid(String gaid); }
public static String getGaid(Context context){ AdInfo adInfo = getAdvertisingIdInfo(context); if(adInfo == null){ return null; } return adInfo.getId(); }
public static void getGaid(Context context, OnGetGaidListener listener) { AdInfo info = getAdvertisingIdInfo(context); if (listener != null) { listener.onGetGaid(info != null ? info.advertisingId : ""); } }
private static AdInfo getAdvertisingIdInfo(Context context) { if(context == null){ Log.e(TAG, "getAdvertisingIdInfo error, context invalid"); return null; }
if (Looper.myLooper() == Looper.getMainLooper()) { Log.e(TAG, "getAdvertisingIdInfo error, cannot be called from the main thread"); return null; }
AdvertisingConnection connection = null; try { PackageManager pm = context.getPackageManager(); pm.getPackageInfo("com.android.vending", 0);
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)) { AdvertisingInterface adInterface = new AdvertisingInterface( connection.getBinder()); Log.d(TAG,"GAID:" + adInterface.getId()); return new AdInfo(adInterface.getId(), adInterface.isLimitAdTrackingEnabled(true)); } } catch (Exception e) { Log.e(TAG,"getAdvertisingIdInfo exception,"+e.getMessage()); return null; } finally { if (connection != null) { context.unbindService(connection); } } return null; }
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 (Throwable e) { Log.e(TAG, "AdvertisingConnection onServiceConnected exception,"+e.getMessage()); } }
public void onServiceDisconnected(ComponentName name) { }
IBinder getBinder() throws InterruptedException { if (this.retrieved) { throw new IllegalStateException(); } this.retrieved = true; return this.queue.take(); } }
private static final class AdvertisingInterface implements IInterface { private IBinder binder;
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; }
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; } }
private 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; } }
}
1、测试过程中如果广告页面打开异常,请检查{appk}和{pid}宏参数是否正确替换。
2、测试过程中没有转化,请检查{did}宏参数是否正确替换,{did}宏参数和用户设备相关,所以需要动态替换。