Unity專案接入Android的Admob Native(原生視訊廣告) SDK
Admob的Native(原生)廣告SDK有Unity版本,但只支援圖文廣告,不支援視訊,為了在Native中加入視訊,只好來接Android的SDK。實現了Unity專案匯出Android Studio工程,AS接入Android SDK後匯出Apk過程。
對於Android小白,這無疑是一個痛苦的過程,遇到了諸多問題,在此記錄一下詳細的入坑過程。還沒能做到AS生成aar包匯入到Unity呼叫,期待大佬秀操作了~~ 每個專案可能發生的問題也不相同,而且Android基礎為零,雖然實現了功能,也難免存在一些問題,僅限於參考,還要以實際問題為主。
1. 先建立Unity專案
2. 建立一個指令碼,用來呼叫Android的函式,要注意的是,呼叫Android的Java介面是直接用的函式名,這裡需要預先定義好所需要的函式名,Unity匯出AS工程後再修改會很麻煩。
public class NativeAdController : MonoBehaviour { [SerializeField] Button showAdBtn; [SerializeField] Button hideAdBtn; AndroidJavaObject jo; void Start () { // Android的Java介面 AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); jo = jc.GetStatic<AndroidJavaObject>("currentActivity"); InitNative(); showAdBtn.onClick.AddListener(ShowNative); hideAdBtn.onClick.AddListener(HideNative); } void InitNative() { //呼叫有參方法 object[] objects = new object[] { "ca-app-pub-3940256099942544~3347511713", "ca-app-pub-3940256099942544/1044960115", false }; jo.Call("initNativeAd", objects); } void ShowNative() { //呼叫有返回值方法 if (jo.Call<bool>("isNativeReady")) jo.Call("showNativeAd"); } void HideNative() { //呼叫無參方法 jo.Call("hideNativeAd"); } }
3. 修改包名,匯出AS包
4. 接下來就是Android Studio的操作了,用AS開啟Unity匯出的專案,後續將在下圖紅色線框內標記的地方進行修改
5. 修改AS工程,在Admob官方給出的演示裡也有說明,不過只劃了重點,沒有詳細過程,慶幸的是,有官方Demo可以參照
5.1 修改"build.gradle"檔案,在下圖示記位置新增程式碼,自動下載引用Android的Admob SDK
maven {
url "https://maven.google.com"
}
implementation 'com.google.android.gms:play-services-ads:17.0.0'
5.2 在"AndroidManifest.xml"檔案中新增Admob的AppId,現在用的是測試Id,正式專案釋出時需要換成正式Id
<meta-data android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-3940256099942544~3347511713"/>
5.3 在"src\main\res"路徑下建立"layout"資料夾,將官方Demo裡相同路徑的"ad_unified.xml" Android UI佈局檔案拖進來,可以嘗試簡單的修改一下相對位置、對齊方式等佈局,如果對Android比較熟,也可以自己寫佈局檔案,Demo佈局檔案如下:
<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>
5.4 如果有下圖的報錯,嘗試改一下Android Plugin Version(貌似是2.3.0版本有問題,我改用了3.2.0)
6. 如果現在還報錯,正常~~,SDK等修改或新增的檔案還沒更新到本地
6.1 點選藍色的報錯,會自動下載更新缺失的檔案
6.2 提示需要將"AndroidManifest.xml"檔案中的"minSdkVersion"屬性移到"build.gradle"檔案中,點選藍色提示自動移動了,但這裡有個問題,自動新增到"build.gradle"的"minSdkVersion"會多一個"=",需要手動刪除"="
6.3 提示"compile"函式名已經棄用,使用"implementation"替代
6.4 重新整理一下,還有一個警告,提醒將"AndroidManifest.xml"檔案中的"targetSdkVersion"屬性移到"build.gradle"檔案中,如果build檔案已有這個屬性了,直接將AndroidManifest裡的刪除就好了
7. 之前在測試的時候,到這裡已經改的差不多了,廣告也可以正常載入到,唯獨視訊不顯示(暫定、靜音按鈕都有),嘗試著改了好多地方都不行,無奈之下與Android的Demo一一對比改過的檔案,最後才發現在"AndroidManifest.xml"中有一個叫作"hardwareAccelerated"(硬體加速)的屬性,設為"true"就可以正常顯示視訊了
8. 到這裡修改的地方都改的差不多了,接下來就是最重要的一步,在"UnityPlayerActivity"腳本里加入Native廣告的邏輯,提供Unity呼叫的函式介面(函式名在Unity專案裡就需要定義好,現在在AS里加入函式邏輯)
8.1 引入名稱空間(按照Unity的叫法了···)
8.2 原有的程式碼不需要修改,在指令碼後面繼續新增廣告邏輯即可,我的廣告邏輯如下,具體怎麼寫要看需求了,重點在於獲取到Native廣告的返回值後,將其展示到Android的UI介面,這裡參照官方Demo做了一下修改:
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 UnityPlayerActivity extends Activity
{
protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code
// Setup activity layout
@Override protected void onCreate(Bundle savedInstanceState)
{
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
mUnityPlayer = new UnityPlayer(this);
setContentView(mUnityPlayer);
mUnityPlayer.requestFocus();
}
//此處省略指令碼原有程式碼
/*API12*/ public boolean onGenericMotionEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); }
// ----- 在後面加入程式碼 -----
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;
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());
}
//顯示廣告,根據Native返回值,顯示到Android介面
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);
}
}
9. 現在已經把所有的工作都做完了,重新整理一下AS,如果沒有報錯的話,就可以打包Apk啦
打包完成後,點選打包成功的提示,開啟"locate"路徑就看到Apk啦,完美~~~