Android插件化突破應用市場沒法上廣告的問題

先簡單的描述一下在廣告方面遇到的問題. 開發一款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 )

相關文章
相關標籤/搜索