前面的Unity项目接入Android的Admob Native(原生视频广告) SDK(一)实现了利用Unity导出AS工程,在AS里接入Android的Admob Native广告(可以播放视频,Unity SDK不支持视频),然后打包Apk。这样已经实现了将Native广告加入到Unity项目,但仍有一个小问题,就是每次出包,都需要导出AS工程,再在AS里接好SDK,然后再打包。每次出包都要接一次SDK,这显然是很不可取的方式。最好的做法是将Native SDK在AS里接好,然后导出一个aar包,在项目需要用到时,导入Unity,加入Native广告,不影响以后的出包方式,可谓是一劳永逸,而且可以将这个导入aar文件在C#里封装好接口,导出一个package,后续需要的项目都可以直接使用了,非常方便。
还是要再说明一下,本人Android小白,对AS/Android/Java了解太少,在网上也没找到合适的案例,虽然实现了功能,过程中难免会出现一些问题。现在看整个过程也很简单,按照流程用不了几十分钟就能搞定,不过从一无所知到实现需求,从零到一,中间的坑也只有遇到了才能体会到无助,虽然过程很痛苦,收获也更多,分享出来,仅供大家参考,有任何不对的地方欢迎指正。另外,非常感谢同事的帮助,才有如今的成果。
下面进入正题:与Unity项目接入Android的Admob Native(原生视频广告) SDK(一)()有很多类似的操作
1. 创建一个空的AS工程:
创建完成进入AS界面,主要将在下面这几个地方进行修改
2. 修改build.gradle文件,有两个不同的这个文件,都需要修改
2.1 在指定位置添加 flatDir { dirs 'libs' }
2.2 在指定位置添加 implementation 'com.google.android.gms:play-services-ads:17.0.0' (另一个文件)
3. 修改 AndroidMainfest.xml 文件
在指定位置添加 <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
添加 <meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-3940256099942544~3347511713"/> (这里用测试Id)
4. 在 libs 文件夹下添加 unity-classes.jar 包
文件路径:Unity安装路径\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\mono\Release\Classes\classes.jar
5. 添加Admob Native官方给出Demo中的Native Ad UI布局文件(代码见文末),如果对Android熟悉,可以修改或自己定义该文件
Native说明: https://developers.google.com/admob/android/native-unified
Demo: https://github.com/googleads/googleads-mobile-android-examples/releases/tag/5.3
加一个id
6. 在MainActivity脚本中加入广告逻辑
6.1 添加引用(类似Unity的引入命名空间)
6.2 修改基类,继承 "UnityPlayerActivity",删除或注释掉Android的默认显示界面(具体代码见文末)
7. 到这里,SDK已经接的差不多了,为了让AS导出aar包,而不是直接导出Apk,还需要修改几个地方
将 AndroidMainfest.xml 文件中的相对路径,改为绝对路径,避免导到Unity后因为找不到Android文件报错
修改后
8.1 删除 AndroidMainfest.xml 文件中的 "android:theme= ··· ···" 语句,删除 style.xml 文件
8.2 删除 AndroidMainfest.xml 中的 "roundIcon" 语句
9. 修改 build.gradle 文件
9.1 修改 'com.android.application' 为 'com.android.library'
9.2 删除 applicationId
10. 看一下 MianAcivity 脚本,如果有一下报错,提示缺少权限,在 AndroidMainfest.xml 中的指定位置加入语句:
"<uses-permission android:name="android.permission.INTERNET" />"
11. 编译一下没有报错就可以导出aar包了
12. 到这里已经完成了AS导出aar的操作
因为AS自动下载的Native SDK并不在项目里,而且不会自动打包到导出的aar包里,需要手动找到SDK的aar文件,与项目导出aar一起添加到Unity工程
在AS的External Libraries可以看到用到的SDK的aar
在下面标注的路径下分别找到SDK用到的aar文件,共有8个(Windows和Mac路径差不多)
找到后先复制到一个文件夹,全部找齐后一起导入Unity(共8个aar)
13.1 创建Unity工程,这里继续使用了Unity项目接入Android的Admob Native(原生视频广告) SDK(一)里的Unity工程
13.2 将 AS 工程里的 AndroidMainfest.xml ,AS导出的aar(app-debug),找到的8个SDK aar 文件一起导入Unity项目 Plugins/Android 文件夹下
13.3 在AS导出的aar(app-debug)中,libs文件夹下有一个 unity-classes.jar(在AS中导入的Unity jar包),因为Unity导出Apk时会自动加入这个jar包,避免因为jar包重复打包出错,需要删除该文件。用打包软件打开jar包,删除unity-classes.jar
14. 设置 Player Settings
其中Minimum API Level 要与AS工程里的 "minSdkVersion" 一致
15. 开始打包,这时候并不会一次成功,检查一下报错,如果是以下报错,因为 "$" 符号报错,这是因为在AS里用的相对路径,这个写法在Unity中不识别
找到导入的aar文件,解压软件打开xml文件,找到报错的一行,将其修改为Unity的Package Name + "mobileadsinitprovider"(忽略划掉的那个红色线框,标记错了)
16. 现在OK了,打包出Apk,测一下如果可以正常显示广告视频,恭喜这就OK了,后面的步骤可以跳过了。
17. 如果视频不显示,如下图:
还是有问题,继续改~ 视频不显示的原因也很简单,就是在 AndroidMainfest.xml 里面有一个 "hardwareAccelerated" 属性,设为true就好了,原因简单,改起来却非常麻烦。因为Unity打包Apk的时候,会自动合并所有的 AndroidMainfest.xml 文件,"hardwareAccelerated" 属性会被自动改为false,也就是说,即使手动将该属性值修改为true,在导出Apk的时候,Unity还是会将其改为false。
不清楚在AS能不能修改后锁定该属性,禁止在Unity中进行修改,或者其他有效的方式,如果有最好了。限于目前没有发现有效的方法,只能再次将Unity工程导出到AS,在AS里修改该属性,然后出Apk。
17.1 导出AS工程,接下来的操作,可以与之前Unity项目接入Android的Admob Native(原生视频广告) SDK(一)差不多,可以参照
17.2 如果有这个报错,检查Android Plugin Version是否是2.3.0,如果是,改为3.2.0
17.3 看一下报错原因,点击蓝色链接自动更新修复
17.4 将 AndroidMainfest.xml 里的 "hardwareAccelerated" 属性设为 true
17.5 看报错原因,继续修改
等编译没有问题,就全部搞定了,打包Apk,大功告成,OKOKOK
上面提到,需要将AS下载的SDK的8个aar文件,手动添加到Unity项目中,还有一种方法,直接导入Admob的Unity SDK,里面已经包含了这8个aar文件。如果项目只需要Native广告,可以选择上面提到的方式,手动找到AS将SDK下载到本地的aar,添加到Unity;如果项目还需要加入Admob的banner等其他广告,则需要导入Admob的Unity SDK。
ad_unified.xml 布局文件
<com.google.android.gms.ads.formats.UnifiedNativeAdView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/nativeAdView"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#FFFFFF"
android:gravity="bottom"
android:minHeight="0dp"
android:orientation="vertical"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:paddingBottom="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="bottom"
android:orientation="vertical"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:paddingTop="3dp"
android:paddingBottom="3dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/ad_app_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:adjustViewBounds="true"
android:paddingEnd="5dp"
android:paddingRight="5dp"
android:paddingBottom="5dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="bottom"
android:orientation="vertical">
<TextView
android:id="@+id/ad_headline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#0000FF"
android:textSize="16sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/ad_advertiser"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="bottom"
android:textSize="14sp"
android:textStyle="bold" />
<RatingBar
android:id="@+id/ad_stars"
style="?android:attr/ratingBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:isIndicator="true"
android:numStars="5"
android:stepSize="0.5" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="bottom"
android:orientation="vertical">
<TextView
android:id="@+id/ad_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:textSize="12sp" />
<ImageView
android:id="@+id/ad_image"
android:layout_width="250dp"
android:layout_height="175dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="0dp" />
<com.google.android.gms.ads.formats.MediaView
android:id="@+id/ad_media"
android:layout_width="250dp"
android:layout_height="175dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="0dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:orientation="horizontal"
android:paddingTop="1dp"
android:paddingBottom="0dp">
<TextView
android:id="@+id/ad_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="5dp"
android:paddingLeft="5dp"
android:paddingEnd="5dp"
android:paddingRight="5dp"
android:textSize="12sp" />
<TextView
android:id="@+id/ad_store"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="5dp"
android:paddingLeft="5dp"
android:paddingEnd="5dp"
android:paddingRight="5dp"
android:textSize="12sp" />
<Button
android:id="@+id/ad_call_to_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</com.google.android.gms.ads.formats.UnifiedNativeAdView>
MainActivity.java 脚本
package com.NativeAdTestDemo.NativeAdTestDemo;
import com.unity3d.player.*;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.Gravity;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RatingBar;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.ads.AdListener;
import com.google.android.gms.ads.AdLoader;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.MobileAds;
import com.google.android.gms.ads.VideoController;
import com.google.android.gms.ads.VideoOptions;
import com.google.android.gms.ads.formats.MediaView;
import com.google.android.gms.ads.formats.NativeAd;
import com.google.android.gms.ads.formats.NativeAdOptions;
import com.google.android.gms.ads.formats.UnifiedNativeAd;
import com.google.android.gms.ads.formats.UnifiedNativeAdView;
import java.util.List;
public class MainActivity extends UnityPlayerActivity
{
@Override protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
}
// ----- 在后面加入代码 -----
private String adUnitId;
private boolean isVideoMute;
private UnifiedNativeAd nativeAd;
private boolean adLoaded;
private boolean isShowAd;
private UnifiedNativeAdView adView;
FrameLayout.LayoutParams layoutParams;
//初始化
public void initNativeAd(String appId, String nativeId, boolean isVideoStartMute)
{
// MobileAds.initialize(this, appId);
MobileAds.initialize(this, "ca-app-pub-3940256099942544~3347511713");
// adUnitId = nativeId;
adUnitId = "ca-app-pub-3940256099942544/1044960115";
isVideoMute = isVideoStartMute;
isShowAd = false;
layoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,FrameLayout.LayoutParams.WRAP_CONTENT);
layoutParams.gravity = Gravity.BOTTOM;
requestNativeAd();
}
//是否加载完成
public boolean isNativeReady()
{
if(adLoaded && nativeAd != null)
return true;
requestNativeAd();
return false;
}
//展示广告
public void showNativeAd()
{
if(isShowAd || !adLoaded || nativeAd == null)
return;
isShowAd = true;
//显示界面
new Thread()
{
public void run() {
//这是耗时操作,完成之后更新UI;
runOnUiThread(new Runnable(){
@Override
public void run() {
if(adView!= null)
mUnityPlayer.removeView(adView);
adView = (UnifiedNativeAdView) getLayoutInflater().inflate(R.layout.ad_unified, null);
populateUnifiedNativeAdView(nativeAd, adView);
mUnityPlayer.addView(adView, layoutParams);
}
});
}
}.start();
adLoaded = false;
}
//隐藏广告
public void hideNativeAd()
{
if (!isShowAd)
return;;
isShowAd = false;
adLoaded = false;
//移除界面
new Thread()
{
public void run() {
runOnUiThread(new Runnable(){
@Override
public void run() {
if(adView != null) {
mUnityPlayer.removeView(adView);
if(nativeAd != null)
nativeAd.destroy();
adView = null;
}
}
});
}
}.start();
requestNativeAd();
}
//加载成功回调
public void onNativeLoadedSuccess()
{
Toast.makeText(UnityPlayerActivity.this, "load native ad successful", Toast.LENGTH_SHORT).show();
}
//加载失败回调
public void onNativeLoadedFail()
{
Toast.makeText(UnityPlayerActivity.this, "Failed to load native", Toast.LENGTH_SHORT).show();
}
//视频播放完成回调
public void onNativeVideoEnd()
{
Toast.makeText(UnityPlayerActivity.this, "Video play end", Toast.LENGTH_SHORT).show();
}
//----------
//请求广告
private void requestNativeAd()
{
adLoaded = false;
// AdLoader.Builder builder = new AdLoader.Builder(this, anUnitId);
//
// builder.forUnifiedNativeAd(new UnifiedNativeAd.OnUnifiedNativeAdLoadedListener() {
@Override
// public void onUnifiedNativeAdLoaded(UnifiedNativeAd unifiedNativeAd) {
// // Show the ad.
// nativeAd = unifiedNativeAd;
// adLoaded = true;
//
// videoStatus.setText("load native ad successful");
// }
// });
//
// VideoOptions videoOptions = new VideoOptions.Builder()
// //是否静音状态开始
// .setStartMuted(false)
// .build();
//
// NativeAdOptions adOptions = new NativeAdOptions.Builder()
// .setVideoOptions(videoOptions)
// .build();
//
// builder.withNativeAdOptions(adOptions);
//
// AdLoader adLoader = builder.withAdListener(new AdListener() {
// @Override
// public void onAdFailedToLoad(int errorCode) {
// // Handle the failure by logging, altering the UI, and so on.
// onNativeLoadedFail();
// }
// }).build();
//
// adLoader.loadAd(new AdRequest.Builder().build());
//
VideoOptions videoOptions = new VideoOptions.Builder()
.setStartMuted(isVideoMute)
.build();
NativeAdOptions adOptions = new NativeAdOptions.Builder()
.setVideoOptions(videoOptions)
.build();
AdLoader adLoader = new AdLoader.Builder(this, adUnitId)
.forUnifiedNativeAd(new UnifiedNativeAd.OnUnifiedNativeAdLoadedListener() {
@Override
public void onUnifiedNativeAdLoaded(UnifiedNativeAd unifiedNativeAd) {
nativeAd = unifiedNativeAd;
adLoaded = true;
onNativeLoadedSuccess();
}
}
)
.withAdListener(new AdListener() {
@Override
public void onAdFailedToLoad(int errorCode) {
onNativeLoadedFail();
}
})
.withNativeAdOptions(adOptions)
.build();
adLoader.loadAd(new AdRequest.Builder().build());
}
//显示广告
private void populateUnifiedNativeAdView(UnifiedNativeAd nativeAd, UnifiedNativeAdView adView)
{
// Get the video controller for the ad. One will always be provided, even if the ad doesn't
// have a video asset.
//获取视频控制器
VideoController vc = nativeAd.getVideoController();
MediaView mediaView = (MediaView)adView.findViewById(R.id.ad_media);
ImageView mainImageView = adView.findViewById(R.id.ad_image);
//是否包含视频资源
if (vc.hasVideoContent())
{
//事件
vc.setVideoLifecycleCallbacks(new VideoController.VideoLifecycleCallbacks() {
@Override
public void onVideoEnd() {
//播放结束
onNativeVideoEnd();
super.onVideoEnd();
}
});
adView.setMediaView(mediaView);
mainImageView.setVisibility(View.GONE);
//adView.getMediaView().setBackgroundColor(Color.rgb(1,1,1));
}
else
{
adView.setImageView(mainImageView);
mediaView.setVisibility(View.GONE);
// At least one image is guaranteed.
List<NativeAd.Image> images = nativeAd.getImages();
mainImageView.setImageDrawable(images.get(0).getDrawable());
}
adView.setHeadlineView(adView.findViewById(R.id.ad_headline));
adView.setBodyView(adView.findViewById(R.id.ad_body));
adView.setCallToActionView(adView.findViewById(R.id.ad_call_to_action));
adView.setIconView(adView.findViewById(R.id.ad_app_icon));
adView.setPriceView(adView.findViewById(R.id.ad_price));
adView.setStarRatingView(adView.findViewById(R.id.ad_stars));
adView.setStoreView(adView.findViewById(R.id.ad_store));
adView.setAdvertiserView(adView.findViewById(R.id.ad_advertiser));
// Some assets are guaranteed to be in every UnifiedNativeAd.
((TextView) adView.getHeadlineView()).setText(nativeAd.getHeadline());
((TextView) adView.getBodyView()).setText(nativeAd.getBody());
((Button) adView.getCallToActionView()).setText(nativeAd.getCallToAction());
// These assets aren't guaranteed to be in every UnifiedNativeAd, so it's important to
// check before trying to display them.
if (nativeAd.getIcon() == null) {
adView.getIconView().setVisibility(View.GONE);
} else {
((ImageView) adView.getIconView()).setImageDrawable(
nativeAd.getIcon().getDrawable());
adView.getIconView().setVisibility(View.VISIBLE);
}
if (nativeAd.getPrice() == null) {
adView.getPriceView().setVisibility(View.INVISIBLE);
} else {
adView.getPriceView().setVisibility(View.VISIBLE);
((TextView) adView.getPriceView()).setText(nativeAd.getPrice());
}
if (nativeAd.getStore() == null) {
adView.getStoreView().setVisibility(View.INVISIBLE);
} else {
adView.getStoreView().setVisibility(View.VISIBLE);
((TextView) adView.getStoreView()).setText(nativeAd.getStore());
}
if (nativeAd.getStarRating() == null) {
adView.getStarRatingView().setVisibility(View.INVISIBLE);
} else {
((RatingBar) adView.getStarRatingView())
.setRating(nativeAd.getStarRating().floatValue());
adView.getStarRatingView().setVisibility(View.VISIBLE);
}
if (nativeAd.getAdvertiser() == null) {
adView.getAdvertiserView().setVisibility(View.INVISIBLE);
} else {
((TextView) adView.getAdvertiserView()).setText(nativeAd.getAdvertiser());
adView.getAdvertiserView().setVisibility(View.VISIBLE);
}
adView.setNativeAd(nativeAd);
}
}