先簡單的描述一下在廣告方面遇到的問題. 開發一款App有了必定的用戶量以後一般會想接入第三方廣告來實現變現, 然而在不少市場不讓這類帶廣告的App上架,除非接的是他們家的廣告.android
在這裏我只能呵呵了.這點困難就想難倒咱們.git
那接下來ShowTime.怎麼作呢? 沒錯,就是插件化. 以廣點通廣告爲例 這裏我使用的是360開源的RePlugin,具體介紹和使用方法請看官方文檔.github
一.RePlugin插件接入指南 第 1 步:添加 RePlugin Plugin Gradle 依賴 在項目根目錄的 build.gradle(注意:不是 app/build.gradle) 中添加 replugin-plugin-gradle 依賴:json
buildscript { dependencies { classpath 'com.qihoo360.replugin:replugin-plugin-gradle:2.2.0' ... } }
第 2 步:添加 RePlugin Plugin Library 依賴 在 app/build.gradle 中應用 replugin-plugin-gradle 插件,並添加 replugin-plugin-lib 依賴:數組
apply plugin: 'replugin-plugin-gradle' dependencies { compile 'com.qihoo360.replugin:replugin-plugin-lib:2.2.0' ... }
接下來您就能夠像正常接入廣告那樣,開發插件。生成出來的是APK,既能夠「安裝到設備」,又能夠「做爲插件」使用。app
二.RePlugin主程序接入指南 第 1 步:添加 RePlugin Host Gradle 依賴 在項目根目錄的 build.gradle(注意:不是 app/build.gradle) 中添加 replugin-host-gradle 依賴:ide
buildscript { dependencies { classpath 'com.qihoo360.replugin:replugin-host-gradle:2.2.0' ... } }
第 2 步:添加 RePlugin Host Library 依賴 在 app/build.gradle 中應用 replugin-host-gradle 插件,並添加 replugin-host-lib 依賴:post
apply plugin: 'replugin-host-gradle' repluginHostConfig { useAppCompat = true } dependencies { ... compile 'com.qihoo360.replugin:replugin-host-lib:2.1.7' }
第 3 步:配置 Application 類fetch
public class App extends Application{ @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); RePlugin.App.attachBaseContext(this); } @Override public void onCreate() { super.onCreate(); RePlugin.App.onCreate(); } }
三.宿主App調用插件廣告 1.編譯插件廣告,將生成的xx.apk包重命名xx.jar 將 xx.jar放到宿主App的 assets/plugins 目錄下 , Replugin將會自動獲取該內置插件gradle
2.處理廣點通開屏廣告 因爲廣點通開屏廣告的展現點擊都由SDK封裝處理了. 咱們這裏採用的方式是,由宿主跳轉到插件的閃屏頁,在插件中完成請求,展現,點擊結束後回到宿主的主頁面. (1)宿主跳轉到插件Activity
try { String config = AssetsUtils.readText(MainActivity.this, "ad_config.json"); Intent intent = new Intent(); intent.setComponent(new ComponentName("adPlugin", "com.plugin.ad.LogoActivity")); intent.putExtra("EXTRA_CONFIG", config); intent.putExtra("EXTRA_POI", POI_FIRST); RePlugin.startActivity(MainActivity.this, intent); } catch (Throwable e) { e.printStackTrace(); }
(2)插件開屏廣告請求處理,就按正常的廣告邏輯走
(3)插件回到宿主的主頁面
private void intoMainPage() { //TODO 打開宿主應用 Intent intent = new Intent(); intent.setClassName("com.wifi.robot", "com.wifi.robot.ui.SecondActivity"); startActivity(intent); finish(); }
(4)宿主的清單文件中添加必要配置,不然廣告無反應
<!-- 廣點通廣告 --> <service android:name="com.qq.e.comm.DownloadService" android:exported="false" /> <activity android:name="com.qq.e.ads.ADActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize" />
注意 : 儘可能使宿主和插件的包名一致,已避免廣告無收益
3.處理廣點通原生廣告 廣點通原生廣告不一樣於開屏廣告,其展現曝光和點擊曝光都由本身處理. 咱們只能經過反射的方案去請求廣告
(1)在插件中先對廣告請求作一層封裝
package com.plugin.ad.managers; import android.app.Activity; import android.content.Context; import android.view.View; import android.view.ViewGroup; import com.plugin.ad.listeners.ILoadListener; import com.plugin.ad.models.ADModel; import com.plugin.ad.models.ModelManager; import com.plugin.ad.utils.JsonUtil; /** * 廣告請求管理類 * Created by wong on 17-8-16. */ public class LoadManager { /** * 初始化 * * @param config */ private static void onInit(String config) { ADModel adModel = JsonUtil.getInstance().fromJson(config, ADModel.class); ModelManager.operationNativeConfig(adModel.place_ids); ModelManager.putAdKey("gdt", adModel.gdt_key); } /** * 請求原生廣告 * * @param context * @param config * @param poi * @param listener */ public static void requestNativeAD(Context context, String config, int poi, final ILoadListener listener) { onInit(config); LoadGdtManager.loadAD(context,poi,listener); } }
/** * 請求廣告 * * @param context 必須是該插件的Context * @param poi * @param listener */ public static void loadAD(Context context, final int poi, final ILoadListener listener) { NativeAD nativeAD = new NativeAD(context, getGdtKey(), getGdtId(poi), new NativeAD.NativeAdListener() { @Override public void onADLoaded(List<NativeADDataRef> list) { if (null != list && !list.isEmpty()) { listener.gdtNativeSuccess(poi, list.get(0)); } else { listener.failure("Empty"); } } @Override public void onNoAD(int i) { listener.failure("onNoAD->" + i); } @Override public void onADStatusChanged(NativeADDataRef nativeADDataRef) { } @Override public void onADError(NativeADDataRef nativeADDataRef, int i) { } }); nativeAD.loadAD(1); }
(2)宿主中反射LoadManager的requestNativeAD()方法 a.拿到插件的ClassLoader
ClassLoader classLoader = RePlugin.fetchClassLoader("adPlugin");
b.取得須要反射的類
Class<?> methodClass = classLoader.loadClass("com.plugin.ad.managers.LoadManager");
c.因爲請求廣告的requestNativeAD()方法中有一個參數是接口. (這裏得使用動態代理) 取得被代理接口
/** * 被代理的接口 */ Class<?> callBackClass = classLoader.loadClass("com.plugin.ad.listeners.ILoadListener"); /** * 這個是動態代理 * callBackClass : 須要被代理的接口的Class * proxListener : 返回的是這個被代理接口的實例 */ Object proxListener = LoadCallBackProx.getInstance(classLoader, callBackClass);
d.接下來就是反射請求接口了
/** * callBackClass : 被代理的接口的Class * proxListener : 被代理接口的實例 */ Method load = methodClass.getDeclaredMethod("requestNativeAD", new Class[]{Context.class, String.class, int.class, callBackClass}); load.invoke(null, RePlugin.fetchContext("adPlugin"), config, poi, proxListener);
注意傳入的Context必須是插件的Context e.在動態代理中取得回調
public class LoadCallBackProx implements InvocationHandler { /** * 這裏的話直接去獲取對象, * 把這個接口的字節碼對象數組扔進來就能夠了 */ public static Object getInstance(ClassLoader classLoader, Class<?> interfaces) { return Proxy.newProxyInstance(classLoader, new Class[]{interfaces}, new LoadCallBackProx()); } /** * @param o * @param method : 具體的方法名稱 * @param objects : 被代理類的回調方法參數 * @return * @throws Throwable */ @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { //判斷是什麼方法被調用了嘛 String methodName = method.getName(); if ("gdtNativeSuccess".equals(methodName)) { int poi = (Integer) objects[0]; Object object = objects[1]; ReAdFactory.putNativeAD(poi, object); EventHelper.post(new ReNativeAdEvent(poi, object, true)); } else if (methodName.equals("failure")) { EventHelper.post(new ReNativeAdEvent(false)); } return null; } }
這裏我使用了EventBus將回調的廣告傳到請求的界面中
/** * 原生信息流 * 插件廣告請求 * * @param event */ @Subscribe public void onEventReADEvent(ReNativeAdEvent event) { if (event.isSucc) { //成功 try { Object object = ReAdFactory.getNativeAD(POI_SECOND); //展現 String title = (String) object.getClass().getMethod("getTitle").invoke(object); String iconUrl = (String) object.getClass().getMethod("getIconUrl").invoke(object); //反射調用曝光接口 object.getClass().getMethod("onExposured", new Class[]{View.class}).invoke(object, findViewById(R.id.activity_main)); } catch (Exception e) { e.printStackTrace(); } } else { //失敗 } }
點擊曝光的反射
/** * 點擊原生信息流廣告 */ private void clickNativeAD() { try { Object object = ReAdFactory.getNativeAD(POI_SECOND); //調用點擊曝光接口 object.getClass().getMethod("onClicked", new Class[]{View.class}).invoke(object, findViewById(R.id.activity_main)); } catch (Exception e) { e.printStackTrace(); } }
四.最後,第一次寫文章,歡迎點評
宿主App : https://github.com/AndWong/RePluginHostForAD/tree/master/app
插件App : https://github.com/AndWong/RePluginHostForAD/tree/master/pluginApp
(ps : 整合代碼到一個工程下並部署到了碼雲上 RePluginForAD )