手把手封装一个 React native Module 之实战封装穿山甲广告模块 android 部分之开屏广告
目前穿山甲已推荐对接聚合 GroMore 广告 SDK,我们已经在 React Native 中完成 GroMore 的对接,访问 http://docs.ads.zmkj6.top 可查看 react-native-gromore 对接文档。
好的,以前公司就有在用穿山甲的广告平台,然后有些项目是 React native ,以前是有封装一个模块,但是中间不只包含穿山甲还有很多其他乱七八糟的模块,干脆边写博客边把头条穿山甲这个模块重写出来吧
首先我们先用 create-react-native-module 工具搭建一个项目吧
create-react-native-module --module-prefix hxf --package-identifier com.haxifang --generate-example oceanengine
我带了一些个性化的参数,create-react-native-module 具体的参数介绍可以看看我另外一篇博客:https://bin.zmide.com/?p=514
我这里就不过多介绍这些东西了,接下来我们可以打开头条穿山甲的广告对接文档,我这里就不把全部文档讲一遍了,相信大家都是有平台账号并且能看到的

头条穿山甲开屏广告大致对接原理的话就是:原生创建一个开屏广告的 Activity ,然后 react native 调用相关方法启动这个 Activity (这里我默认就是认为你有 android 开发基础的哈,也不去讲这些 android 开发基础了,当然没基础的话也可以跟着一步步对接,相关模块我也后续会放到 github ,希望各位小伙伴能给个 star ,我也会把相关对接文档补全的…)首先我们知道 react native 有两种模块形式:Native Modules 和 Native UI Components,
Native Modules 就是暴露一些相关的方法提供给 RN 去调用一些原生的特殊 api
Native UI Components 的话就是一个 UI 组件可以嵌入到布局中的
我们通过上面的对接分析可以知道开屏广告的话就不属于 UI 组件的范围,而是通过原生这边去跳转原生的 Activity 去达到一个开屏的效果。
属于 UI 组件的话大概是像 信息流广告,Banner 广告,Draw 信息流等等…这种类型的。以后我也会讲到怎么去写这一类的组件的…
好了前期的开发思路分析就到这里,我们看看刚刚生成的 hxf-oceanengine
模块,打开代码我们看看这个模块的开发目录结构

我们看到有 4 个文件夹,android( 这就是 android 的开发目录了 ),example(这是演示项目目录,可以用来调试我们的模块) ,ios(这是 ios 模块对应的开发目录),scripts(这是 srcipts 目录用来放 js 文件的吧,它里面写了一个事例和注释,有兴趣的小伙伴可以了解一下)
我这里就主要讲 android 模块的实现,所以 ios 文件夹我们就不去看了,还有一个就是 example 我们在里面调试模块的可用性吧
我先配置一下 example 演示项目吧,它和我们的 react native 项目是没有什么区别的 ,可以把它先跑起来看看


启动项目并没有什么骚操作,正常的进入到 example 文件夹后 react-native run-android 就能跑起来了
事例项目的话引入的方式是通过 node module 方式,这样会使我们用 Android studio 修改调试 module 的代码会非常不方便,于是我们需要手动 link 一下 hxf-byted-ad 模块,手动 link 后我们会看到 example 的 android/settings.gradle 文件中多了两行代码
include ':hxf-byted-ad'
project(':hxf-byted-ad').projectDir = new File(rootProject.projectDir, '../node_modules/hxf-byted-ad')
需要把它指向我们的模块下面的 android 目录,改成如下代码
include ':hxf-byted-ad'
project(':hxf-byted-ad').projectDir = new File(rootProject.projectDir, '../../android')
接下来就可以用 Android studio 打开我们的 example 下面的 android 目录了,打开后大概能看到如下的目录结构

现在我们就可以通过 Android studio 随意的修改 Android hxf-byted-ad 的代码啦
前期工作差不多了,现在我们下载头条穿山甲提供给我们的 arr 格式的 SDK 模块包,在我们的 hxf-byted-ad 的 android 文件夹下创建一个 libs 文件夹,将在穿山甲下载的 arr 文件放到此目录下

打开 build.gradle 文件,添加引入
....
dependencies {
....
implementation fileTree(dir: 'libs', include: ['*.aar'])
....
}
....
我还是贴一张图吧,我这里还引用了 com.github.bumptech.glide 模块用来处理图片资源的,如果你没有用到的话是可以不需要引入的

接下来就是广告平台要求的权限和一些需要的配置,编辑我们的 AndroidManifest.xml 文件,我这里直接贴我配置的了,详细的配置可以看文档
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.haxifang">
<!--必要权限-->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!--可选权限-->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.GET_TASKS"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application>
<activity android:name=".ttad.modules.SplashActivity" />
<provider
android:name="com.bytedance.sdk.openadsdk.TTFileProvider"
android:authorities="${applicationId}.TTFileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<provider
android:name="com.bytedance.sdk.openadsdk.multipro.TTMultiProvider"
android:authorities="${applicationId}.TTMultiProvider"
android:exported="false" />
</application>
</manifest>
这里有用到一个 @xml/file_paths 文件,我们需要在 res 文件夹中创建一个 xml 的目录然后添加一个 file_paths.xml 文件,代码如下
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="tt_external_root" path="." />
<external-path name="tt_external_download" path="Download" />
<external-files-path name="tt_external_files_download" path="Download" />
<files-path name="tt_internal_file_download" path="Download" />
<cache-path name="tt_internal_cache_download" path="Download" />
</paths>

创建好配置文件后我们可以看到 java 文件夹中已经有两个系统帮我们创建好的类
BytedAdModule,是一个 Native Modules 包的实现类
BytedAdPackage,我们写好的 Native Modules 或者 Native UI Components 都是要通过这个类来注册到 react native 中去的
好的,我们简单了解了一下这两个类的用途后先不管它两,我们先创建一个 SplashActivity 和 tt_splash_activity.xml 用来写我们开屏广告的代码

这里我先贴上我实现的代码,就是很简单的实现一个开屏广告,你也可以直接把穿山甲提供的 Demo 中的代码用起来
package com.haxifang.ttad.modules;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import com.bytedance.sdk.openadsdk.AdSlot;
import com.bytedance.sdk.openadsdk.TTAdConfig;
import com.bytedance.sdk.openadsdk.TTAdConstant;
import com.bytedance.sdk.openadsdk.TTAdNative;
import com.bytedance.sdk.openadsdk.TTAdSdk;
import com.bytedance.sdk.openadsdk.TTSplashAd;
import com.haxifang.R;
public class SplashActivity extends Activity {
static String TAG = "头条开屏广告";
static String ttAppId;
private TTAdNative mTTAdNative;
private FrameLayout mSplashContainer;
// 是否强制跳转到主页面
private boolean mForceGoMain;
// 开屏广告加载超时时间,建议大于1000,这里为了冷启动第一次加载到广告并且展示,示例设置了2000ms
private static final int AD_TIME_OUT = 2000;
private static final int MSG_GO_MAIN = 1;
// 开屏广告是否已经加载
private boolean mHasLoaded;
private String code_id;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tt_splash_activity);
// 读取 code id
Bundle extras = getIntent().getExtras();
ttAppId = extras.getString("appid");
code_id = extras.getString("codeid");
// 初始化广告 SDK
initAD();
// 在合适的时机申请权限,如read_phone_state,防止获取不了 imei 时候,下载类广告没有填充的问题
// 在开屏时候申请不太合适,因为该页面倒计时结束或者请求超时会跳转,在该页面申请权限,体验不好
// TTAdManagerHolder.getInstance(this).requestPermissionIfNecessary(this);
// 初始化自定义广告 View
initView();
// 加载开屏广告
loadSplashAd();
}
private void initAD() {
TTAdSdk.init(this, new TTAdConfig.Builder()
.appId(ttAppId)
.useTextureView(true) //使用TextureView控件播放视频,默认为SurfaceView,当有SurfaceView冲突的场景,可以使用TextureView
.appName("答妹")
.titleBarTheme(TTAdConstant.TITLE_BAR_THEME_DARK)
.allowShowNotify(true) //是否允许sdk展示通知栏提示
.allowShowPageWhenScreenLock(true) //是否在锁屏场景支持展示广告落地页
.debug(true) //测试阶段打开,可以通过日志排查问题,上线时去除该调用
.directDownloadNetworkType(TTAdConstant.NETWORK_STATE_WIFI, TTAdConstant.NETWORK_STATE_3G) //允许直接下载的网络状态集合
.supportMultiProcess(false)
.build());
mTTAdNative = TTAdSdk.getAdManager().createAdNative(this);
}
// 初始化开屏广告 View
private void initView() {
// 初始化广告渲染组件
mSplashContainer = this.findViewById(R.id.splash_container);
}
// 加载开屏广告方法
private void loadSplashAd() {
// 创建开屏广告请求参数 AdSlot ,具体参数含义参考文档
AdSlot adSlot = new AdSlot.Builder()
.setCodeId(code_id)
.setSupportDeepLink(true)
.setImageAcceptedSize(1080, 1920)
.build();
// 请求广告,调用开屏广告异步请求接口,对请求回调的广告作渲染处理
mTTAdNative.loadSplashAd(adSlot, new TTAdNative.SplashAdListener() {
@Override
@MainThread
public void onError(int code, String message) {
// 广告渲染失败
Log.d(TAG, message);
mHasLoaded = true;
showToast(message + " - " + code_id);
// 关闭开屏广告
goToMainActivity();
}
@Override
@MainThread
public void onTimeout() {
// 开屏广告渲染超时
mHasLoaded = true;
showToast("加载超时");
// 关闭开屏广告
goToMainActivity();
}
@Override
@MainThread
public void onSplashAdLoad(TTSplashAd ad) {
Log.d(TAG, "开屏广告请求成功");
mHasLoaded = true;
if (ad == null) {
// 未知错误获取到的广告对象为空,关闭广告
goToMainActivity();
return;
}
// 获取SplashView
View view = ad.getSplashView();
mSplashContainer.removeAllViews();
// 把SplashView 添加到ViewGroup中,注意开屏广告view:width >=70%屏幕宽;height >=50%屏幕宽
mSplashContainer.addView(view);
// 设置不开启开屏广告倒计时功能以及不显示跳过按钮,如果这么设置,您需要自定义倒计时逻辑
// ad.setNotAllowSdkCountdown();
// 设置SplashView的交互监听器
ad.setSplashInteractionListener(new TTSplashAd.AdInteractionListener() {
@Override
public void onAdClicked(View view, int type) {
Log.d(TAG, "onAdClicked");
// showToast("开屏广告点击");
}
@Override
public void onAdShow(View view, int type) {
Log.d(TAG, "onAdShow");
// showToast("开屏广告展示");
}
@Override
public void onAdSkip() {
// returnIntent.putExtra("onAdSkip", true);
Log.d(TAG, "onAdSkip");
// showToast("开屏广告跳过");
goToMainActivity();
}
@Override
public void onAdTimeOver() {
Log.d(TAG, "onAdTimeOver");
// showToast("开屏广告倒计时结束");
goToMainActivity();
}
});
}
}, AD_TIME_OUT);
}
// 关闭开屏广告方法
private void goToMainActivity() {
if(mSplashContainer != null) {
mSplashContainer.removeAllViews();
}
this.overridePendingTransition(0, 0); // 不要过渡动画
this.finish();
}
private void showToast(String msg) {
// TToast.show(this, "splash:" + msg);
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#FFFFFF">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.8"
android:orientation="vertical"
>
<FrameLayout
android:id="@+id/splash_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
>
</FrameLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.2"
android:gravity="center"
android:orientation="vertical"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:gravity="center"
android:textColor="#AAA"
android:text="正在启动中…"/>
</LinearLayout>
</LinearLayout>
开屏实现好后,我们把这个模块暴露给 RN 吧,回到我们的 BytedAdModule 类中,就是我们上面看的系统帮我们创建的,改成如下代码我们将暴露一个 loadSplashAd 方法给 RN 该方法接收两个参数 appid 和 codeid(这个可以到穿山甲平台申请),我们给这个模块命名为 BytedADSplash
package com.haxifang;
import android.app.Activity;
import android.content.Intent;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;
import com.haxifang.ttad.TTAdManagerHolder;
import com.haxifang.ttad.modules.SplashActivity;
public class BytedAdModule extends ReactContextBaseJavaModule {
private final ReactApplicationContext reactContext;
public BytedAdModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}
@Override
public String getName() {
return "BytedADSplash";
}
@ReactMethod
public void loadSplashAd(String appid, String codeid) {
Intent intent = new Intent(reactContext, SplashActivity.class);
try {
intent.putExtra("codeid", codeid);
intent.putExtra("appid", codeid);
final Activity context = getCurrentActivity();
context.overridePendingTransition(0, 0); // 不要过渡动画
context.startActivityForResult(intent, 10000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
然后进入到 BytedAdPackage 类的 createNativeModules 方法中注册这个模块
package com.haxifang;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
public class BytedAdPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new BytedAdModule(reactContext));
return modules;
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
最后我们回到模块的 index.js 在项目的根目录下,导出我们的启动开屏广告模块
import { NativeModules } from "react-native";
const { BytedADSplash } = NativeModules;
export const loadSplashAd = (appid, codeid) => {
BytedADSplash.loadSplashAd(appid, codeid);
};
export default { loadSplashAd };
然后到 example 项目下执行 rm -rf node_modules/ && yarn && react-native run-android
在 example 中记得导入和使用 loadSplashAd 来启动开屏广告哦,最后别忘记传入 appid 和 codeid (开屏广告位id)…
import {loadSplashAd} from 'hxf-byted-ad';
....
// 在你想要启动开屏广告的地方调用
loadSplashAd(appid, codeid);
....
以上代码均已开源到 react-native-ad 感谢各位慷慨的 Star。
好啦,我们下篇博客见鸭,有问题随时可以给我留言回复哦,我看到一定会回复的
大佬,github地址呢,我去star
公司大佬给整到一个广告集成包里了,可以看看:https://github.com/haxibiao/react-native-ad