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
- WCF技术剖析之二十二: 深入剖析WCF底层异常处理框架实现原理[中篇]
- 《Enterprise Library深入解析与灵活应用》博文系列汇总
- 使命必达: 深入剖析WCF的可靠会话[概念篇]
- AngularJS in Action读书笔记2——view和controller的那些事儿
- WCF技术剖析之二十一:WCF基本异常处理模式[中篇]
- 小程序上线“小游戏”,正式引爆3.0社交红利
- 漫谈人工智能机器翻译的前世今生
- 并发与实例上下文模式: WCF服务在不同实例上下文模式下具有怎样的并发表现
- 区块链将变革的五个行业
- WCF技术剖析之二十三:服务实例(Service Instance)生命周期如何控制[上篇]
- AngularJS in Action读书笔记3——走近Services
- 有了这些无人驾驶的汽车,未来还需要考驾照吗?
- 并发与实例上下文模式: WCF服务在不同实例上下文模式下具有怎样的并发表现
- AngularJS in Action读书笔记4(实战篇)——创建Statistic模块
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- ABAP,Java和JavaScript类的构造函数使用的一些陷阱
- Spring Security如何优雅的增加OAuth2协议授权模式
- 如何将自定义XML视图注入SAP Fiori Elements应用
- SAP UI5控件ID的生成逻辑原理解析
- 如何自定义SAP Spartacus店铺的界面颜色风格
- 如何自定义SAP Spartacus店铺的购物车图表css风格
- 数据库PostrageSQL-从源代码安装
- Django的中间件
- WPF 使用 Skia 绘制 WriteableBitmap 图片
- dotnet 在 UOS 国产系统上使用 MonoDevelop 创建 GTK 全平台带界面应用
- dotnet 在 UOS 国产系统上安装 MonoDevelop 开发工具
- 使用SAP Spartacus快速创建一个电商店铺网站
- 使用StackBlitz和SAP Spartacus快速创建电商店铺页面
- SAP CRM Interactive Report(交互式报表)里和服务订单相关的一些字段
- SAP S/4HANA Customer Management(CRM)模块的扩展性设计