Android插件化突破应用市场无法上广告的问题

先简单的描述一下在广告方面遇到的问题.
开发一款App有了一定的用户量之后通常会想接入第三方广告来实现变现,
然而在很多市场不让这类带广告的App上架,除非接的是他们家的广告.

在这里我只能呵呵了.这点困难就想难倒我们.

那接下来ShowTime.怎么做呢?
没错,就是插件化.
以广点通广告为例
这里我使用的是360开源的RePlugin,具体介绍和使用方法请看官方文档.

一.RePlugin插件接入指南
第 1 步:添加 RePlugin Plugin Gradle 依赖
在项目根目录的 build.gradle(注意:不是 app/build.gradle) 中添加 replugin-plugin-gradle 依赖:

 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,既可以“安装到设备”,又可以“作为插件”使用。

二.RePlugin主程序接入指南
第 1 步:添加 RePlugin Host Gradle 依赖
在项目根目录的 build.gradle(注意:不是 app/build.gradle) 中添加 replugin-host-gradle 依赖:

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 依赖:

apply plugin: 'replugin-host-gradle'
repluginHostConfig {
    useAppCompat = true
}
dependencies {
    ...
    compile 'com.qihoo360.replugin:replugin-host-lib:2.1.7'
}

第 3 步:配置 Application 类

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将会自动获取该内置插件

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容