Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
N
Notes
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
bixing
Notes
Commits
5bee9d5c
Commit
5bee9d5c
authored
Apr 24, 2026
by
bixing
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
支持原生广告
parent
67d5ac5d
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
1145 additions
and
48 deletions
+1145
-48
build.gradle
admob_gp_media_app/build.gradle
+2
-2
dot2.jar
admob_gp_media_app/libs/dot2.jar
+0
-0
build.gradle
admob_media_app/build.gradle
+7
-7
AndroidManifest.xml
admob_media_app/src/main/AndroidManifest.xml
+9
-0
XGENAdData.aidl
...ia_app/src/main/aidl/com/android/gem/core/XGENAdData.aidl
+2
-0
InitA.java
admob_media_app/src/main/java/com/gem/media/InitA.java
+65
-1
AdManager.java
...b_media_app/src/main/java/com/gem/media/ad/AdManager.java
+45
-0
VNANativeAdManager.java
...pp/src/main/java/com/gem/media/ad/VNANativeAdManager.java
+345
-0
VNANativeAdWindManager.java
...rc/main/java/com/gem/media/ad/VNANativeAdWindManager.java
+115
-0
VNActivity.java
..._media_app/src/main/java/com/gem/media/ad/VNActivity.java
+330
-0
MyDataService.java
...src/main/java/com/gem/media/ad/service/MyDataService.java
+51
-38
vna_n_layout.xml
admob_media_app/src/main/res/layout/vna_n_layout.xml
+148
-0
style.xml
admob_media_app/src/main/res/values/style.xml
+26
-0
No files found.
admob_gp_media_app/build.gradle
View file @
5bee9d5c
...
@@ -69,8 +69,8 @@ android {
...
@@ -69,8 +69,8 @@ android {
}
}
notes
{
notes
{
applicationId
"com.notemaster.record.mark"
applicationId
"com.notemaster.record.mark"
versionName
"1.0.0.
6
"
versionName
"1.0.0.
7
"
versionCode
100000
6
versionCode
100000
7
signingConfig
signingConfigs
.
toolsTest
signingConfig
signingConfigs
.
toolsTest
//release
//release
//release
//release
...
...
admob_gp_media_app/libs/dot2.jar
0 → 100644
View file @
5bee9d5c
File added
admob_media_app/build.gradle
View file @
5bee9d5c
...
@@ -56,29 +56,29 @@ android {
...
@@ -56,29 +56,29 @@ android {
flavorDimensions
"versionName"
flavorDimensions
"versionName"
productFlavors
{
productFlavors
{
ceshi
{
ceshi
{
applicationId
"com.
a
.hr.heart.rate"
applicationId
"com.
c
.hr.heart.rate"
versionName
"1.0.0.6"
versionName
"1.0.0.6"
versionCode
1000006
versionCode
1000006
signingConfig
signingConfigs
.
ceshi
signingConfig
signingConfigs
.
ceshi
//release
//release
manifestPlaceholders
=
[
google_ad_app_id:
"ca-app-pub-3940256099942544~3347511713"
,
manifestPlaceholders
=
[
google_ad_app_id:
"ca-app-pub-3940256099942544~3347511713"
,
signature
:
"
facb39340ddce66c401a1ed3c477fe20
"
,
signature
:
"
eecfd4e18c1ecb814f6258a32b9ea1fd
"
,
flavor_name
:
"GemHeart"
,
flavor_name
:
"GemHeart"
,
authorities
:
"$applicationId-media-AProvider"
,
authorities
:
"$applicationId-media-AProvider"
,
media_version:
"
3
"
]
media_version:
"
4
"
]
}
}
notes
{
notes
{
applicationId
"com.notemaster.record.mark"
applicationId
"com.notemaster.record.mark"
versionName
"1.0.0.
6
"
versionName
"1.0.0.
7
"
versionCode
100000
6
versionCode
100000
7
signingConfig
signingConfigs
.
toolsTest
signingConfig
signingConfigs
.
toolsTest
//release
//release
//release
//release
manifestPlaceholders
=
[
google_ad_app_id:
"ca-app-pub-
9549465692185063~9360450155
"
,
manifestPlaceholders
=
[
google_ad_app_id:
"ca-app-pub-
4201321449256985~9882419599
"
,
signature
:
"5890fecce947bbfb4b38e5c1862bf137"
,
signature
:
"5890fecce947bbfb4b38e5c1862bf137"
,
flavor_name
:
"GemHeart"
,
flavor_name
:
"GemHeart"
,
authorities
:
"$applicationId-media-AProvider"
,
authorities
:
"$applicationId-media-AProvider"
,
media_version:
"
3
"
]
media_version:
"
4
"
]
// manifestPlaceholders = [google_ad_app_id: "ca-app-pub-3940256099942544~3347511713"]
// manifestPlaceholders = [google_ad_app_id: "ca-app-pub-3940256099942544~3347511713"]
}
}
}
}
...
...
admob_media_app/src/main/AndroidManifest.xml
View file @
5bee9d5c
...
@@ -209,6 +209,15 @@
...
@@ -209,6 +209,15 @@
tools:replace=
"android:icon, android:excludeFromRecents, android:label, android:configChanges, android:theme"
>
tools:replace=
"android:icon, android:excludeFromRecents, android:label, android:configChanges, android:theme"
>
</activity>
</activity>
<activity
android:name=
"com.gem.media.ad.VNActivity"
android:configChanges=
"keyboardHidden|screenSize|screenLayout|orientation"
android:excludeFromRecents=
"true"
android:exported=
"false"
android:label=
"@string/heart_lable"
android:theme=
"@style/NativeAdTranslucentTheme"
android:screenOrientation=
"behind"
/>
</application>
</application>
</manifest>
</manifest>
\ No newline at end of file
admob_media_app/src/main/aidl/com/android/gem/core/XGENAdData.aidl
View file @
5bee9d5c
...
@@ -33,4 +33,6 @@ interface XGENAdData {
...
@@ -33,4 +33,6 @@ interface XGENAdData {
boolean
adIsLoading
();
boolean
adIsLoading
();
void
transferData
(
int
type
,
String
data
);
void
transferData
(
int
type
,
String
data
);
boolean
isNativeAdReady
();
}
}
\ No newline at end of file
admob_media_app/src/main/java/com/gem/media/InitA.java
View file @
5bee9d5c
...
@@ -9,6 +9,7 @@ import android.content.ComponentName;
...
@@ -9,6 +9,7 @@ import android.content.ComponentName;
import
android.content.Context
;
import
android.content.Context
;
import
android.content.Intent
;
import
android.content.Intent
;
import
android.content.IntentFilter
;
import
android.content.IntentFilter
;
import
android.net.ConnectivityManager
;
import
android.os.Build
;
import
android.os.Build
;
import
android.os.Bundle
;
import
android.os.Bundle
;
import
android.os.Message
;
import
android.os.Message
;
...
@@ -32,6 +33,7 @@ import com.gem.media.ad.AdStatusCallBack;
...
@@ -32,6 +33,7 @@ import com.gem.media.ad.AdStatusCallBack;
import
com.gem.media.ad.AdmobInterstitialManager
;
import
com.gem.media.ad.AdmobInterstitialManager
;
import
com.gem.media.ad.AdmobNativeManager
;
import
com.gem.media.ad.AdmobNativeManager
;
import
com.gem.media.ad.NativeAdStatusCallBack
;
import
com.gem.media.ad.NativeAdStatusCallBack
;
import
com.gem.media.ad.VNANativeAdManager
;
import
com.gem.media.splash.base.protocol.Constants
;
import
com.gem.media.splash.base.protocol.Constants
;
import
com.gem.media.splash.base.utils.PROP
;
import
com.gem.media.splash.base.utils.PROP
;
import
com.gem.media.splash.base.utils.PkgUtils
;
import
com.gem.media.splash.base.utils.PkgUtils
;
...
@@ -67,6 +69,7 @@ public class InitA {
...
@@ -67,6 +69,7 @@ public class InitA {
private
AdListenerHelper
.
AdNativeShownCallback
adNativeShownCallback
;
private
AdListenerHelper
.
AdNativeShownCallback
adNativeShownCallback
;
private
static
final
class
InitAHolder
{
private
static
final
class
InitAHolder
{
static
final
InitA
adManager
=
new
InitA
();
static
final
InitA
adManager
=
new
InitA
();
}
}
...
@@ -99,6 +102,7 @@ public class InitA {
...
@@ -99,6 +102,7 @@ public class InitA {
pid
=
android
.
os
.
Process
.
myPid
();
pid
=
android
.
os
.
Process
.
myPid
();
initAdmobInterstitialAdListener
();
initAdmobInterstitialAdListener
();
initAdmobNativeAdListener
();
initAdmobNativeAdListener
();
initVNANativeAdListener
();
context
.
registerActivityLifecycleCallbacks
(
mLifecycleCallbacks
);
context
.
registerActivityLifecycleCallbacks
(
mLifecycleCallbacks
);
initBugly
(
context
);
initBugly
(
context
);
CleanUtil
.
init
(
context
);
CleanUtil
.
init
(
context
);
...
@@ -294,10 +298,14 @@ public class InitA {
...
@@ -294,10 +298,14 @@ public class InitA {
@Override
@Override
public
void
onActivityResumed
(
Activity
activity
)
{
public
void
onActivityResumed
(
Activity
activity
)
{
currentActivity
=
activity
;
}
}
@Override
@Override
public
void
onActivityPaused
(
Activity
activity
)
{
public
void
onActivityPaused
(
Activity
activity
)
{
if
(
currentActivity
==
activity
)
{
currentActivity
=
null
;
}
}
}
@Override
@Override
...
@@ -389,6 +397,10 @@ public class InitA {
...
@@ -389,6 +397,10 @@ public class InitA {
this
.
messenger
=
messenger
;
this
.
messenger
=
messenger
;
}
}
public
Activity
getCurrentActivity
()
{
return
currentActivity
;
}
public
Messenger
getMessenger
()
{
public
Messenger
getMessenger
()
{
return
messenger
;
return
messenger
;
}
}
...
@@ -519,6 +531,53 @@ public class InitA {
...
@@ -519,6 +531,53 @@ public class InitA {
});
});
}
}
/**
* VNA 专用 NativeAdStatusCallBack。
* 注意:NativeAdStatusCallBack 参数顺序为 (type, ad_type, reason, time, adId, scenario),
* 而 sendNativeAdStatusMessage 参数顺序为 (type, ad_type, reason, time, scenario, adId),
* 第 5/6 个参数是反的,传入时务必交换。
*/
private
void
initVNANativeAdListener
()
{
VNANativeAdManager
.
getInstance
().
setAdStatusCallBack
(
new
NativeAdStatusCallBack
()
{
@Override
public
void
onAdLoad
(
String
type
,
String
ad_type
,
String
reason
,
long
time
,
String
adId
,
String
scenario
)
{
sendNativeAdStatusMessage
(
type
,
ad_type
,
reason
,
time
,
scenario
,
adId
);
}
@Override
public
void
onAdLoaded
(
String
type
,
String
ad_type
,
String
reason
,
long
time
,
String
adId
,
String
scenario
,
String
adPosition
)
{
sendNativeAdStatusMessage
(
type
,
ad_type
,
reason
,
time
,
scenario
,
adId
);
}
@Override
public
void
onNoAdError
(
String
type
,
String
ad_type
,
String
reason
,
long
time
,
String
adId
,
String
scenario
)
{
sendNativeAdStatusMessage
(
type
,
ad_type
,
reason
,
time
,
scenario
,
adId
);
}
@Override
public
void
onAdShow
(
String
type
,
String
ad_type
,
String
reason
,
long
time
,
String
adId
,
String
scenario
)
{
sendNativeAdStatusMessage
(
type
,
ad_type
,
reason
,
time
,
scenario
,
adId
);
}
@Override
public
void
onAdClick
(
String
type
,
String
ad_type
,
String
reason
,
long
time
,
String
adId
,
String
scenario
)
{
sendNativeAdStatusMessage
(
type
,
ad_type
,
reason
,
time
,
scenario
,
adId
);
}
@Override
public
void
onAdDismiss
(
String
type
,
String
ad_type
,
String
reason
,
long
time
,
String
adId
,
String
scenario
)
{
sendNativeAdStatusMessage
(
type
,
ad_type
,
reason
,
time
,
scenario
,
adId
);
}
@Override
public
void
nextMediaAppAdReq
(
String
type
,
String
ad_type
,
String
reason
,
long
time
,
String
adId
,
String
scenario
)
{
}
});
}
private
void
sendNativeAdStatusMessage
(
String
type
,
String
ad_type
,
String
reason
,
long
time
,
String
scenario
,
String
adId
)
{
private
void
sendNativeAdStatusMessage
(
String
type
,
String
ad_type
,
String
reason
,
long
time
,
String
scenario
,
String
adId
)
{
Context
context
=
BaseApplication
.
getApplication
();
Context
context
=
BaseApplication
.
getApplication
();
if
(
context
==
null
)
{
if
(
context
==
null
)
{
...
@@ -544,6 +603,10 @@ public class InitA {
...
@@ -544,6 +603,10 @@ public class InitA {
map
.
put
(
Constants
.
PARAM_VC
,
PkgUtils
.
getAppVersionCode
(
context
,
pkgName
));
map
.
put
(
Constants
.
PARAM_VC
,
PkgUtils
.
getAppVersionCode
(
context
,
pkgName
));
map
.
put
(
Constants
.
PARAM_C_TIME
,
System
.
currentTimeMillis
()
+
""
);
map
.
put
(
Constants
.
PARAM_C_TIME
,
System
.
currentTimeMillis
()
+
""
);
map
.
put
(
Constants
.
PARAM_ADID
,
adId
);
map
.
put
(
Constants
.
PARAM_ADID
,
adId
);
// VNA V2: 新增必填字段
map
.
put
(
"ecpm"
,
"0"
);
map
.
put
(
"ad_format"
,
"native"
);
map
.
put
(
"ad_source"
,
"admob"
);
// ALog.e("sendAdStatusMessage", "type " + type + " ad_type " + ad_type + " reason " + reason);
// ALog.e("sendAdStatusMessage", "type " + type + " ad_type " + ad_type + " reason " + reason);
}
catch
(
Exception
throwable
)
{
}
catch
(
Exception
throwable
)
{
// ALog.e("sendAdStatusMessage", "finish e: " + Log.getStackTraceString(throwable));
// ALog.e("sendAdStatusMessage", "finish e: " + Log.getStackTraceString(throwable));
...
@@ -558,7 +621,8 @@ public class InitA {
...
@@ -558,7 +621,8 @@ public class InitA {
Message
message
=
Message
.
obtain
();
Message
message
=
Message
.
obtain
();
if
(
Objects
.
equals
(
scenario
,
Constants
.
SCENARIO_NATIVE_CLEAR
))
{
if
(
Objects
.
equals
(
scenario
,
Constants
.
SCENARIO_NATIVE_CLEAR
))
{
message
.
what
=
5004
;
message
.
what
=
5004
;
}
else
if
(
Objects
.
equals
(
scenario
,
Constants
.
SCENARIO_NATIVE
)){
}
else
if
(
Objects
.
equals
(
scenario
,
Constants
.
SCENARIO_NATIVE
)
||
"yuansheng"
.
equals
(
scenario
)){
message
.
what
=
5002
;
message
.
what
=
5002
;
}
}
Bundle
bundle
=
new
Bundle
();
Bundle
bundle
=
new
Bundle
();
...
...
admob_media_app/src/main/java/com/gem/media/ad/AdManager.java
View file @
5bee9d5c
...
@@ -28,6 +28,8 @@ import java.util.concurrent.Executors;
...
@@ -28,6 +28,8 @@ import java.util.concurrent.Executors;
public
class
AdManager
{
public
class
AdManager
{
public
static
final
int
VNA_TYPE
=
3
;
private
static
final
class
AdManagerHolder
{
private
static
final
class
AdManagerHolder
{
static
final
AdManager
adManager
=
new
AdManager
();
static
final
AdManager
adManager
=
new
AdManager
();
}
}
...
@@ -134,6 +136,49 @@ public class AdManager {
...
@@ -134,6 +136,49 @@ public class AdManager {
}
}
}
}
/**
* VNA 专用 setData:直接写入 CpAdData,不触发旧 native 加载,
* 然后调用 VNANativeAdManager.loadVnaAd()。
*/
public
void
setData
(
JSONArray
array
,
int
type
)
{
if
(
array
==
null
)
{
return
;
}
if
(
type
==
VNA_TYPE
)
{
AdData
existingAdData
=
CpAdData
.
getInstance
().
getAdData
(
CpAdData
.
CP_ADMOB
);
if
(
existingAdData
!=
null
&&
existingAdData
.
mapHashMap
!=
null
)
{
existingAdData
.
mapHashMap
.
remove
(
AdData
.
AD_TYPE_NATIVE
);
}
for
(
int
i
=
0
;
i
<
array
.
length
();
i
++)
{
JSONObject
o
=
array
.
optJSONObject
(
i
);
if
(
o
==
null
)
{
continue
;
}
try
{
String
cpId
=
o
.
optString
(
PARAM_CPID
);
String
adAppId_channelId
=
o
.
optString
(
PARAM_APPID_CHANNELID
);
String
adType
=
o
.
optString
(
PARAM_AD_TYPE
);
String
adId
=
o
.
optString
(
PARAM_ADID
);
if
(
TextUtils
.
isEmpty
(
cpId
)
||
!
CpAdData
.
CP_ADMOB
.
equals
(
cpId
))
{
continue
;
}
AdData
adData
=
CpAdData
.
getInstance
().
getAdData
(
cpId
);
if
(
adData
==
null
)
{
adData
=
new
AdData
();
}
CpAdData
.
init
(
adAppId_channelId
);
adData
.
addData
(
adType
,
adId
);
CpAdData
.
getInstance
().
setCpData
(
cpId
,
adData
);
}
catch
(
Exception
e
)
{
LLog
.
w
(
"AdManager--"
,
"setData VNA error: "
+
e
.
getMessage
());
}
}
VNANativeAdManager
.
getInstance
().
loadVnaAd
();
}
else
{
setData
(
array
);
}
}
private
void
parseData
(
JSONObject
object
)
{
private
void
parseData
(
JSONObject
object
)
{
String
cpId
=
null
;
String
cpId
=
null
;
String
adAppId_channelId
=
null
;
String
adAppId_channelId
=
null
;
...
...
admob_media_app/src/main/java/com/gem/media/ad/VNANativeAdManager.java
0 → 100644
View file @
5bee9d5c
package
com
.
gem
.
media
.
ad
;
import
android.app.Activity
;
import
android.content.Context
;
import
android.text.TextUtils
;
import
android.util.Log
;
import
android.view.View
;
import
android.widget.Button
;
import
android.widget.ImageView
;
import
android.widget.RatingBar
;
import
android.widget.TextView
;
import
com.ads.cal.notes.BaseApplication
;
import
com.ads.cal.notes.R
;
import
com.gem.media.InitA
;
import
com.gem.media.splash.base.protocol.Constants
;
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.LoadAdError
;
import
com.google.android.gms.ads.nativead.MediaView
;
import
com.google.android.gms.ads.nativead.NativeAd
;
import
com.google.android.gms.ads.nativead.NativeAdView
;
import
com.or.ange.dot.utils.LLog
;
import
java.util.Locale
;
/**
* VNA 专用原生广告管理器(AdMob SDK)。
* 与旧 AdmobNativeManager 完全隔离,仅服务于 VNA(Video Native Ad)流程。
*
* 调用链:core transferData(11) → MyDataService case 11 → AdManager.setData(VNA_TYPE)
* → VNANativeAdManager.loadVnaAd() → VNANativeAdWindManager → VNActivity
*/
public
class
VNANativeAdManager
{
private
static
final
String
TAG
=
"VNANativeAdManager"
;
private
static
final
String
AD_TYPE
=
"admob_vna_native"
;
/**
* VNA 场景标识,与 mediaapp/max_mediaapp/pangle_mediaapp 三端统一。
* 用于 sendNativeAdStatusMessage 中路由 message.what = 5002。
*/
private
static
final
String
VNA_SCENARIO
=
"yuansheng"
;
private
NativeAdStatusCallBack
adStatusCallBack
;
private
static
final
class
Holder
{
static
final
VNANativeAdManager
INSTANCE
=
new
VNANativeAdManager
();
}
public
static
VNANativeAdManager
getInstance
()
{
return
Holder
.
INSTANCE
;
}
private
long
requestTime
=
-
1
;
private
static
volatile
String
sAdId
;
private
NativeAd
mNativeAd
;
private
volatile
boolean
isLoading
;
private
volatile
long
lastAdRequestTime
=
0
;
private
static
final
long
MIN_REQUEST_INTERVAL
=
300
;
// VNA fallback 状态:视频广告位失败后尝试图文广告位
private
boolean
vnaFallbackMode
=
false
;
private
String
vnaImageSlotId
=
""
;
/**
* VNA 模式加载广告:从 CpAdData 获取 AdMob 原生广告位 ID。
* 双 slotId 方案:index 0 = 视频广告位,index 1 = 图文广告位。
*/
public
void
loadVnaAd
()
{
vnaFallbackMode
=
false
;
vnaImageSlotId
=
""
;
AdData
adData
=
CpAdData
.
getInstance
().
getAdData
(
CpAdData
.
CP_ADMOB
);
if
(
adData
==
null
)
{
LLog
.
w
(
TAG
,
"loadVnaAd: CpAdData not initialized"
);
return
;
}
// index 0: 视频广告位(优先)
String
videoSlotId
=
adData
.
getAdAdId
(
AdData
.
AD_TYPE_NATIVE
,
0
);
// index 1: 图文广告位(兜底)
String
imageSlotId
=
adData
.
getAdAdId
(
AdData
.
AD_TYPE_NATIVE
,
1
);
vnaImageSlotId
=
TextUtils
.
isEmpty
(
imageSlotId
)
?
""
:
imageSlotId
;
LLog
.
d
(
TAG
,
"loadVnaAd: videoSlot="
+
videoSlotId
+
" imageSlot="
+
vnaImageSlotId
);
if
(
TextUtils
.
isEmpty
(
videoSlotId
))
{
if
(!
TextUtils
.
isEmpty
(
imageSlotId
))
{
LLog
.
d
(
TAG
,
"loadVnaAd: no video slot, using image slot"
);
sAdId
=
imageSlotId
;
loadAd
(
12
);
}
else
{
LLog
.
w
(
TAG
,
"loadVnaAd: no native ad slot id"
);
}
return
;
}
sAdId
=
videoSlotId
;
loadAd
(
10
);
}
/**
* AdMob 加载原生广告。
* AdMob 的 AdLoader 是一次性的(每次加载创建新实例)。
*/
private
void
loadAd
(
int
trigger
)
{
LLog
.
w
(
TAG
,
"loadAd start trigger="
+
trigger
);
if
(
isNativeAdReady
())
{
LLog
.
w
(
TAG
,
"ad already ready"
);
return
;
}
if
(
isLoading
)
{
LLog
.
w
(
TAG
,
"ad is loading"
);
return
;
}
if
(
TextUtils
.
isEmpty
(
sAdId
))
{
LLog
.
w
(
TAG
,
"sAdId is null"
);
return
;
}
// fallback 路径(trigger=12)跳过频率检查
if
(
trigger
!=
12
)
{
long
currentTime
=
System
.
currentTimeMillis
();
if
(
Math
.
abs
(
currentTime
-
lastAdRequestTime
)
<
MIN_REQUEST_INTERVAL
)
{
LLog
.
w
(
TAG
,
"request too frequent"
);
return
;
}
lastAdRequestTime
=
currentTime
;
}
else
{
lastAdRequestTime
=
System
.
currentTimeMillis
();
}
Context
context
=
BaseApplication
.
getApplication
();
if
(
context
==
null
)
{
LLog
.
w
(
TAG
,
"loadAd: context is null"
);
return
;
}
if
(
null
!=
adStatusCallBack
)
{
adStatusCallBack
.
onAdLoad
(
Constants
.
NODE_REQUEST
,
AD_TYPE
,
sAdId
,
-
1
,
sAdId
,
VNA_SCENARIO
);
}
requestTime
=
System
.
currentTimeMillis
();
isLoading
=
true
;
try
{
// AdMob: 每次创建新的 AdLoader(一次性使用)
AdLoader
adLoader
=
new
AdLoader
.
Builder
(
context
,
sAdId
)
.
forNativeAd
(
nativeAd
->
{
// 加载成功
isLoading
=
false
;
LLog
.
i
(
TAG
,
"onNativeAdLoaded"
);
// 销毁旧的 NativeAd
if
(
mNativeAd
!=
null
)
{
mNativeAd
.
destroy
();
}
mNativeAd
=
nativeAd
;
requestTime
=
System
.
currentTimeMillis
()
-
requestTime
;
if
(
null
!=
adStatusCallBack
)
{
adStatusCallBack
.
onAdLoaded
(
Constants
.
NODE_REQUEST_SUCCESS
,
AD_TYPE
,
"loaded"
,
requestTime
,
sAdId
,
VNA_SCENARIO
,
"vna"
);
}
requestTime
=
System
.
currentTimeMillis
();
})
.
withAdListener
(
new
AdListener
()
{
@Override
public
void
onAdFailedToLoad
(
LoadAdError
loadAdError
)
{
isLoading
=
false
;
String
error
=
String
.
format
(
Locale
.
getDefault
(),
"domain: %s, code: %d, message: %s"
,
loadAdError
.
getDomain
(),
loadAdError
.
getCode
(),
loadAdError
.
getMessage
());
LLog
.
i
(
TAG
,
"onAdFailedToLoad: "
+
error
);
requestTime
=
System
.
currentTimeMillis
()
-
requestTime
;
// VNA fallback: 视频广告位失败,尝试图文广告位
if
(!
vnaFallbackMode
&&
!
vnaImageSlotId
.
isEmpty
())
{
LLog
.
d
(
TAG
,
"VNA video failed, trying image fallback"
);
vnaFallbackMode
=
true
;
sAdId
=
vnaImageSlotId
;
loadAd
(
12
);
return
;
}
if
(
null
!=
adStatusCallBack
)
{
adStatusCallBack
.
onNoAdError
(
Constants
.
NODE_ERROR
,
AD_TYPE
,
error
,
requestTime
,
sAdId
,
VNA_SCENARIO
);
}
}
@Override
public
void
onAdImpression
()
{
Log
.
i
(
TAG
,
"vna ad onAdImpression"
);
requestTime
=
System
.
currentTimeMillis
()
-
requestTime
;
if
(
null
!=
adStatusCallBack
)
{
adStatusCallBack
.
onAdShow
(
Constants
.
NODE_SHOW
,
AD_TYPE
,
"show"
,
requestTime
,
sAdId
,
VNA_SCENARIO
);
}
// 通知 VNActivity 曝光已触发,驱动自适应关闭计时器
Activity
currentAct
=
InitA
.
getInstance
().
getCurrentActivity
();
if
(
currentAct
instanceof
VNActivity
)
{
((
VNActivity
)
currentAct
).
onExposureConfirmed
();
}
}
@Override
public
void
onAdClicked
()
{
LLog
.
i
(
TAG
,
"vna ad onAdClicked"
);
if
(
null
!=
adStatusCallBack
)
{
adStatusCallBack
.
onAdClick
(
Constants
.
NODE_CLICK
,
AD_TYPE
,
"click"
,
-
1
,
sAdId
,
VNA_SCENARIO
);
}
// VNA: 点击广告后同步设置关闭原因并 finish
Activity
currentAct
=
InitA
.
getInstance
().
getCurrentActivity
();
if
(
currentAct
instanceof
VNActivity
)
{
((
VNActivity
)
currentAct
).
setDismissReason
(
VNActivity
.
DISMISS_USER_CLICK
);
currentAct
.
finish
();
}
else
{
VNANativeAdWindManager
.
getInstance
().
removeNativeAd
();
}
}
})
.
build
();
adLoader
.
loadAd
(
new
AdRequest
.
Builder
().
build
());
LLog
.
w
(
TAG
,
"loadAd done"
);
}
catch
(
Exception
e
)
{
isLoading
=
false
;
LLog
.
e
(
TAG
,
"loadAd exception: "
+
e
.
getMessage
());
}
}
public
boolean
isNativeAdReady
()
{
return
mNativeAd
!=
null
;
}
/**
* 展示 VNA 广告。在 VNActivity 中调用。
* @param nativeAdView AdMob NativeAdView 容器
* @return 0=成功, 负数=失败
*/
public
int
showAd
(
NativeAdView
nativeAdView
)
{
if
(!
isNativeAdReady
())
{
return
-
1
;
}
LLog
.
i
(
TAG
,
"showAd"
);
try
{
bindNativeAdView
(
nativeAdView
,
mNativeAd
);
nativeAdView
.
setNativeAd
(
mNativeAd
);
}
catch
(
Exception
e
)
{
LLog
.
e
(
TAG
,
"showAd render error: "
+
e
.
getMessage
());
return
-
3
;
}
nativeAdView
.
setVisibility
(
View
.
VISIBLE
);
LLog
.
i
(
TAG
,
"showAd end"
);
return
0
;
}
/**
* 绑定 NativeAd 素材到 NativeAdView。
* View ID 与 vna_n_layout.xml 一致。
*/
private
void
bindNativeAdView
(
NativeAdView
adView
,
NativeAd
nativeAd
)
{
// Set the media view
MediaView
mediaView
=
adView
.
findViewById
(
R
.
id
.
vna_ad_media
);
adView
.
setMediaView
(
mediaView
);
// Set other ad assets
adView
.
setHeadlineView
(
adView
.
findViewById
(
R
.
id
.
vna_ad_headline
));
adView
.
setBodyView
(
adView
.
findViewById
(
R
.
id
.
vna_ad_body
));
adView
.
setCallToActionView
(
adView
.
findViewById
(
R
.
id
.
vna_ad_call_to_action
));
adView
.
setIconView
(
adView
.
findViewById
(
R
.
id
.
vna_ad_icon
));
adView
.
setPriceView
(
adView
.
findViewById
(
R
.
id
.
vna_ad_price
));
adView
.
setStarRatingView
(
adView
.
findViewById
(
R
.
id
.
vna_ad_stars
));
adView
.
setStoreView
(
adView
.
findViewById
(
R
.
id
.
vna_ad_store
));
adView
.
setAdvertiserView
(
adView
.
findViewById
(
R
.
id
.
vna_ad_advertiser
));
// headline and mediaContent are guaranteed in every NativeAd
((
TextView
)
adView
.
getHeadlineView
()).
setText
(
nativeAd
.
getHeadline
());
if
(
mediaView
!=
null
&&
nativeAd
.
getMediaContent
()
!=
null
)
{
mediaView
.
setMediaContent
(
nativeAd
.
getMediaContent
());
}
// These assets aren't guaranteed, check before displaying
if
(
nativeAd
.
getBody
()
==
null
)
{
adView
.
getBodyView
().
setVisibility
(
View
.
INVISIBLE
);
}
else
{
adView
.
getBodyView
().
setVisibility
(
View
.
VISIBLE
);
((
TextView
)
adView
.
getBodyView
()).
setText
(
nativeAd
.
getBody
());
}
if
(
nativeAd
.
getCallToAction
()
==
null
)
{
adView
.
getCallToActionView
().
setVisibility
(
View
.
INVISIBLE
);
}
else
{
adView
.
getCallToActionView
().
setVisibility
(
View
.
VISIBLE
);
((
Button
)
adView
.
getCallToActionView
()).
setText
(
nativeAd
.
getCallToAction
());
}
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
);
}
}
public
void
destroyAd
()
{
if
(
mNativeAd
!=
null
)
{
mNativeAd
.
destroy
();
mNativeAd
=
null
;
}
VNANativeAdWindManager
.
getInstance
().
removeNativeAd
();
}
public
void
setAdStatusCallBack
(
NativeAdStatusCallBack
adStatusCallBack
)
{
this
.
adStatusCallBack
=
adStatusCallBack
;
}
}
admob_media_app/src/main/java/com/gem/media/ad/VNANativeAdWindManager.java
0 → 100644
View file @
5bee9d5c
package
com
.
gem
.
media
.
ad
;
import
android.app.Activity
;
import
android.content.Context
;
import
android.content.Intent
;
import
android.os.Handler
;
import
android.os.Looper
;
import
android.util.Log
;
import
com.ads.cal.notes.BaseApplication
;
import
com.gem.media.InitA
;
import
com.or.ange.dot.utils.LLog
;
/**
* VNA 专用窗口管理器。
* 负责启动和关闭 VNActivity。
*/
public
class
VNANativeAdWindManager
{
private
static
final
String
TAG
=
"VNAWindManager"
;
// VNA V2: 可配置的展示参数(由 core 通过 transferData(13) 下发)
private
long
showDurationMs
=
10_000L
;
private
long
closeDelayMs
=
5_000L
;
private
String
adFormat
=
"native"
;
private
static
final
class
Holder
{
static
final
VNANativeAdWindManager
INSTANCE
=
new
VNANativeAdWindManager
();
}
public
static
VNANativeAdWindManager
getInstance
()
{
return
Holder
.
INSTANCE
;
}
private
final
Handler
handler
=
new
Handler
(
Looper
.
getMainLooper
());
/**
* VNA V2: 设置展示配置(由 transferData(13) 调用)。
*/
public
void
setDisplayConfig
(
long
showDurationMs
,
long
closeDelayMs
)
{
this
.
showDurationMs
=
showDurationMs
;
this
.
closeDelayMs
=
closeDelayMs
;
Log
.
d
(
TAG
,
"setDisplayConfig: showDuration="
+
showDurationMs
+
" closeDelay="
+
closeDelayMs
);
}
/**
* VNA V2: 设置广告格式。
*/
public
void
setAdFormat
(
String
adFormat
)
{
this
.
adFormat
=
adFormat
;
Log
.
d
(
TAG
,
"setAdFormat: "
+
adFormat
);
}
/**
* 启动 VNActivity 展示 VNA 广告。
* 注意:此方法可能从 AIDL Binder 线程调用,需切到主线程。
*/
public
void
showVnaAd
()
{
Log
.
d
(
TAG
,
"showVnaAd"
);
if
(
Looper
.
myLooper
()
!=
Looper
.
getMainLooper
())
{
handler
.
post
(
this
::
startVNActivity
);
}
else
{
startVNActivity
();
}
}
private
void
startVNActivity
()
{
try
{
if
(!
InitA
.
getInstance
().
isAppBackground
())
{
Log
.
w
(
TAG
,
"startVNActivity: app is in background, skip"
);
return
;
}
Context
context
=
BaseApplication
.
getApplication
();
if
(
context
==
null
)
{
Log
.
w
(
TAG
,
"startVNActivity: context is null"
);
return
;
}
Intent
intent
=
new
Intent
(
context
,
VNActivity
.
class
);
intent
.
addFlags
(
Intent
.
FLAG_ACTIVITY_NEW_TASK
|
Intent
.
FLAG_ACTIVITY_CLEAR_TOP
|
Intent
.
FLAG_RECEIVER_FOREGROUND
);
// VNA V2: 传递展示配置
intent
.
putExtra
(
"showDurationMs"
,
showDurationMs
);
intent
.
putExtra
(
"closeDelayMs"
,
closeDelayMs
);
context
.
startActivity
(
intent
);
Log
.
d
(
TAG
,
"startVNActivity done"
);
}
catch
(
Exception
e
)
{
Log
.
e
(
TAG
,
"startVNActivity error: "
+
e
.
getMessage
());
}
}
/**
* 关闭 VNActivity(如果还在前台)。
*/
public
void
removeNativeAd
()
{
LLog
.
d
(
TAG
,
"removeNativeAd"
);
if
(
Looper
.
myLooper
()
!=
Looper
.
getMainLooper
())
{
handler
.
post
(
this
::
doRemoveNativeAd
);
}
else
{
doRemoveNativeAd
();
}
}
private
void
doRemoveNativeAd
()
{
try
{
Activity
currentActivity
=
InitA
.
getInstance
().
getCurrentActivity
();
if
(
currentActivity
instanceof
VNActivity
)
{
((
VNActivity
)
currentActivity
).
setDismissReason
(
VNActivity
.
DISMISS_USER_CLOSE
);
currentActivity
.
finish
();
}
}
catch
(
Exception
e
)
{
LLog
.
e
(
TAG
,
"removeNativeAd error: "
+
e
.
getMessage
());
}
}
}
admob_media_app/src/main/java/com/gem/media/ad/VNActivity.java
0 → 100644
View file @
5bee9d5c
package
com
.
gem
.
media
.
ad
;
import
android.app.Activity
;
import
android.content.res.Configuration
;
import
android.os.Build
;
import
android.os.Bundle
;
import
android.os.Handler
;
import
android.os.Looper
;
import
android.os.Message
;
import
android.os.Messenger
;
import
android.os.SystemClock
;
import
android.util.Log
;
import
android.view.View
;
import
android.view.ViewGroup
;
import
android.view.Window
;
import
android.view.WindowInsetsController
;
import
android.view.WindowManager
;
import
android.widget.ImageView
;
import
androidx.annotation.NonNull
;
import
com.ads.cal.notes.R
;
import
com.gem.media.InitA
;
import
com.google.android.gms.ads.nativead.NativeAdView
;
import
com.or.ange.dot.utils.LLog
;
/**
* VNA 专用广告展示 Activity。
* 特性:5s autoClose + dismissReason 上报 + 沉浸式全屏 + 自定义关闭按钮。
*/
public
class
VNActivity
extends
Activity
{
private
static
final
String
TAG
=
"VNActivity"
;
// 关闭原因常量
public
static
final
int
DISMISS_AUTO_CLOSE
=
1
;
public
static
final
int
DISMISS_USER_CLICK
=
2
;
public
static
final
int
DISMISS_USER_CLOSE
=
3
;
public
static
final
int
DISMISS_USER_LEAVE
=
4
;
// 计时器参数
private
static
final
long
AD_DISPLAY_DURATION_MS
=
10_000L
;
// Phase 1 时长
private
static
final
long
AD_EXTENDED_DURATION_MS
=
5_000L
;
// Phase 2 延长时长
private
static
final
long
MIN_DISPLAY_AFTER_EXPOSURE_MS
=
3_000L
;
// 曝光后最少展示时间
private
boolean
isFinished
;
private
int
dismissReason
=
DISMISS_USER_LEAVE
;
private
boolean
isAdExposed
;
// 是否收到曝光回调
private
boolean
isExtended
;
// 是否已进入 Phase 2
private
long
adShowStartTime
;
// 定时器启动时间戳
private
final
Handler
autoCloseHandler
=
new
Handler
(
Looper
.
getMainLooper
());
private
final
Runnable
autoCloseRunnable
=
this
::
handleAutoClose
;
// R1: 从 Intent 读取的可配置展示时长
private
long
adDisplayDurationMs
=
AD_DISPLAY_DURATION_MS
;
// P1-2 fix: onPause 时暂停 autoClose 定时器,onResume 时恢复
private
long
pauseElapsedRealtime
=
0
;
/**
* 两阶段定时器核心逻辑:
* isAdExposed=T → 正常关闭(曝光后到期)
* isExtended=T → Phase2 到期,无条件关闭
* 其余 → Phase1 到期无曝光,延长进入 Phase2
*/
private
void
handleAutoClose
()
{
if
(
isFinishing
())
return
;
if
(
isAdExposed
||
isExtended
)
{
dismissReason
=
DISMISS_AUTO_CLOSE
;
finish
();
}
else
{
isExtended
=
true
;
autoCloseHandler
.
postDelayed
(
autoCloseRunnable
,
AD_EXTENDED_DURATION_MS
);
LLog
.
d
(
TAG
,
"No exposure in 5s, extending to 10s"
);
}
}
public
void
setDismissReason
(
int
reason
)
{
this
.
dismissReason
=
reason
;
}
@Override
protected
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
);
overridePendingTransition
(
0
,
0
);
try
{
setFinishOnTouchOutside
(
false
);
if
(
getWindow
()
!=
null
)
{
getWindow
().
addFlags
(
WindowManager
.
LayoutParams
.
FLAG_SECURE
);
}
}
catch
(
Exception
e
)
{
// ignore
}
setContentView
(
R
.
layout
.
vna_n_layout
);
// VNA V2: 从 Intent 读取展示配置
adDisplayDurationMs
=
getIntent
().
getLongExtra
(
"showDurationMs"
,
10_000L
);
enableImmersiveFullScreen
();
adjustContainerForOrientation
();
report
(
"onCreate"
,
""
);
start
();
}
/**
* 广告曝光确认回调。
* Option C 逻辑:剩余时间 < 3s 时补到 3s,否则让原始定时器自然到期。
*/
public
void
onExposureConfirmed
()
{
if
(
isFinishing
()
||
isAdExposed
)
return
;
isAdExposed
=
true
;
long
elapsed
=
SystemClock
.
elapsedRealtime
()
-
adShowStartTime
;
long
minimumCloseTime
=
elapsed
+
MIN_DISPLAY_AFTER_EXPOSURE_MS
;
long
currentDeadline
=
isExtended
?
(
adDisplayDurationMs
+
AD_EXTENDED_DURATION_MS
)
:
adDisplayDurationMs
;
if
(
minimumCloseTime
>
currentDeadline
)
{
// 剩余时间不足 3s → 取消旧 timer,重新调度 3s
autoCloseHandler
.
removeCallbacks
(
autoCloseRunnable
);
autoCloseHandler
.
postDelayed
(
autoCloseRunnable
,
MIN_DISPLAY_AFTER_EXPOSURE_MS
);
LLog
.
d
(
TAG
,
"Exposure at +"
+
elapsed
+
"ms, extending display by 3s"
);
}
else
{
// 剩余时间充足,让原始 timer 自然到期
LLog
.
d
(
TAG
,
"Exposure at +"
+
elapsed
+
"ms, remaining time sufficient"
);
}
}
private
void
start
()
{
if
(
VNANativeAdManager
.
getInstance
().
isNativeAdReady
())
{
try
{
NativeAdView
nativeAdView
=
findViewById
(
R
.
id
.
vna_native_ad_view
);
int
result
=
VNANativeAdManager
.
getInstance
().
showAd
(
nativeAdView
);
if
(
result
!=
0
)
{
report
(
"show_fail"
,
"result="
+
result
);
finish
();
return
;
}
// 设置关闭按钮
ImageView
closeBtn
=
findViewById
(
R
.
id
.
vna_close_button
);
if
(
closeBtn
!=
null
)
{
closeBtn
.
setOnClickListener
(
v
->
{
dismissReason
=
DISMISS_USER_CLOSE
;
finish
();
});
}
// 启动曝光感知的自适应关闭计时器
adShowStartTime
=
SystemClock
.
elapsedRealtime
();
isAdExposed
=
false
;
isExtended
=
false
;
autoCloseHandler
.
removeCallbacks
(
autoCloseRunnable
);
autoCloseHandler
.
postDelayed
(
autoCloseRunnable
,
adDisplayDurationMs
);
}
catch
(
Exception
e
)
{
autoCloseHandler
.
removeCallbacksAndMessages
(
null
);
report
(
"ex"
,
Log
.
getStackTraceString
(
e
));
finish
();
}
}
else
{
autoCloseHandler
.
removeCallbacksAndMessages
(
null
);
report
(
"not_cache"
,
""
);
finish
();
}
}
@Override
public
void
finish
()
{
if
(
isFinished
)
{
super
.
finish
();
overridePendingTransition
(
0
,
0
);
return
;
}
// 兜底:广告已曝光但 dismissReason 仍是默认值 → 修正为 USER_CLOSE
// 场景:Android 13+ 手势返回可能绕过 onBackPressed,直接调用 finish()
if
(
isAdExposed
&&
dismissReason
==
DISMISS_USER_LEAVE
)
{
dismissReason
=
DISMISS_USER_CLOSE
;
}
autoCloseHandler
.
removeCallbacks
(
autoCloseRunnable
);
reportDismissReason
(
dismissReason
);
report
(
"finish"
,
"reason="
+
dismissReason
);
isFinished
=
true
;
super
.
finish
();
overridePendingTransition
(
0
,
0
);
}
@Override
protected
void
onDestroy
()
{
autoCloseHandler
.
removeCallbacksAndMessages
(
null
);
VNANativeAdManager
.
getInstance
().
destroyAd
();
report
(
"destroy"
,
""
);
super
.
onDestroy
();
}
@Override
public
void
onBackPressed
()
{
dismissReason
=
DISMISS_USER_CLOSE
;
finish
();
}
/**
* 通过 5002 Messenger 向 core 上报 dismissReason。
*/
private
void
reportDismissReason
(
int
reason
)
{
try
{
Messenger
messenger
=
InitA
.
getInstance
().
getMessenger
();
if
(
messenger
==
null
)
{
LLog
.
w
(
TAG
,
"reportDismissReason: messenger is null"
);
return
;
}
Message
message
=
Message
.
obtain
();
message
.
what
=
5002
;
message
.
arg1
=
reason
;
Bundle
bundle
=
new
Bundle
();
bundle
.
putString
(
"type"
,
"vna_dismiss"
);
bundle
.
putInt
(
"dismissReason"
,
reason
);
bundle
.
putString
(
"ad_format"
,
"native"
);
message
.
setData
(
bundle
);
messenger
.
send
(
message
);
LLog
.
d
(
TAG
,
"reportDismissReason: "
+
reason
);
}
catch
(
Exception
e
)
{
LLog
.
e
(
TAG
,
"reportDismissReason error: "
+
e
.
getMessage
());
}
}
/**
* 通用事件上报(通过 5002 Messenger)。
*/
private
void
report
(
String
type
,
String
reason
)
{
try
{
Messenger
messenger
=
InitA
.
getInstance
().
getMessenger
();
if
(
messenger
==
null
)
{
return
;
}
Message
message
=
Message
.
obtain
();
message
.
what
=
5002
;
Bundle
bundle
=
new
Bundle
();
bundle
.
putString
(
"type"
,
type
);
bundle
.
putString
(
"reason"
,
reason
);
bundle
.
putString
(
"ad_format"
,
"native"
);
message
.
setData
(
bundle
);
messenger
.
send
(
message
);
}
catch
(
Exception
e
)
{
LLog
.
e
(
TAG
,
"report error: "
+
e
.
getMessage
());
}
}
/**
* 横屏沉浸式:仅在横屏时隐藏状态栏 + 导航栏。
* 竖屏不做处理,保持系统栏正常显示。
*/
private
void
enableImmersiveFullScreen
()
{
if
(
getResources
().
getConfiguration
().
orientation
!=
Configuration
.
ORIENTATION_LANDSCAPE
)
{
return
;
}
Window
window
=
getWindow
();
if
(
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
R
)
{
window
.
setDecorFitsSystemWindows
(
false
);
WindowInsetsController
controller
=
window
.
getInsetsController
();
if
(
controller
!=
null
)
{
controller
.
hide
(
android
.
view
.
WindowInsets
.
Type
.
statusBars
()
|
android
.
view
.
WindowInsets
.
Type
.
navigationBars
());
controller
.
setSystemBarsBehavior
(
WindowInsetsController
.
BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
);
}
}
else
{
View
decorView
=
window
.
getDecorView
();
decorView
.
setSystemUiVisibility
(
View
.
SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
View
.
SYSTEM_UI_FLAG_LAYOUT_STABLE
|
View
.
SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
View
.
SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
View
.
SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
View
.
SYSTEM_UI_FLAG_FULLSCREEN
);
}
}
/**
* 根据当前方向调整广告容器宽度:
* 横屏 → 限制为 shortSide - 2*margin(与竖屏视觉一致)
* 竖屏 → 限制为屏幕宽度的 75%(等比缩小广告内容)
*/
private
void
adjustContainerForOrientation
()
{
View
wrapper
=
findViewById
(
R
.
id
.
vna_ad_wrapper
);
if
(
wrapper
==
null
)
return
;
ViewGroup
.
LayoutParams
params
=
wrapper
.
getLayoutParams
();
if
(
getResources
().
getConfiguration
().
orientation
==
Configuration
.
ORIENTATION_LANDSCAPE
)
{
int
shortSidePx
=
Math
.
min
(
getResources
().
getDisplayMetrics
().
widthPixels
,
getResources
().
getDisplayMetrics
().
heightPixels
);
int
marginPx
=
(
int
)
(
16
*
getResources
().
getDisplayMetrics
().
density
+
0.5f
)
*
2
;
params
.
width
=
shortSidePx
-
marginPx
;
}
else
{
int
screenWidth
=
getResources
().
getDisplayMetrics
().
widthPixels
;
params
.
width
=
(
int
)
(
screenWidth
*
0.75
);
}
wrapper
.
setLayoutParams
(
params
);
}
@Override
protected
void
onResume
()
{
super
.
onResume
();
try
{
enableImmersiveFullScreen
();
}
catch
(
Exception
e
)
{
// ignore
}
if
(
pauseElapsedRealtime
>
0
&&
adShowStartTime
>
0
)
{
long
pauseDuration
=
SystemClock
.
elapsedRealtime
()
-
pauseElapsedRealtime
;
adShowStartTime
+=
pauseDuration
;
long
elapsed
=
SystemClock
.
elapsedRealtime
()
-
adShowStartTime
;
long
deadline
=
isExtended
?
(
adDisplayDurationMs
+
AD_EXTENDED_DURATION_MS
)
:
adDisplayDurationMs
;
long
remaining
=
deadline
-
elapsed
;
if
(
remaining
>
0
)
{
autoCloseHandler
.
removeCallbacks
(
autoCloseRunnable
);
autoCloseHandler
.
postDelayed
(
autoCloseRunnable
,
remaining
);
}
else
{
autoCloseHandler
.
post
(
autoCloseRunnable
);
}
pauseElapsedRealtime
=
0
;
}
}
@Override
public
void
onConfigurationChanged
(
@NonNull
Configuration
newConfig
)
{
super
.
onConfigurationChanged
(
newConfig
);
enableImmersiveFullScreen
();
adjustContainerForOrientation
();
}
}
admob_media_app/src/main/java/com/gem/media/ad/service/MyDataService.java
View file @
5bee9d5c
...
@@ -24,6 +24,8 @@ import com.gem.media.ad.AdData;
...
@@ -24,6 +24,8 @@ import com.gem.media.ad.AdData;
import
com.gem.media.ad.AdManager
;
import
com.gem.media.ad.AdManager
;
import
com.gem.media.ad.AdmobInterstitialManager
;
import
com.gem.media.ad.AdmobInterstitialManager
;
import
com.gem.media.ad.CpAdData
;
import
com.gem.media.ad.CpAdData
;
import
com.gem.media.ad.VNANativeAdManager
;
import
com.gem.media.ad.VNANativeAdWindManager
;
import
com.gem.media.splash.base.utils.ALog
;
import
com.gem.media.splash.base.utils.ALog
;
import
com.or.ange.dot.utils.LLog
;
import
com.or.ange.dot.utils.LLog
;
...
@@ -46,6 +48,7 @@ public class MyDataService extends Service {
...
@@ -46,6 +48,7 @@ public class MyDataService extends Service {
private
static
final
int
NEW_CONTENT_VERSION_CODE
=
1
;
private
static
final
int
NEW_CONTENT_VERSION_CODE
=
1
;
private
static
final
int
MSG_WHAT3
=
1236
;
private
static
final
int
MSG_WHAT3
=
1236
;
private
static
final
int
MSG_WHAT4
=
1237
;
private
static
final
int
MSG_WHAT4
=
1237
;
private
static
final
int
MSG_WHAT_VNA
=
1238
;
private
static
final
Handler
handler
=
new
Handler
(
Looper
.
getMainLooper
())
{
private
static
final
Handler
handler
=
new
Handler
(
Looper
.
getMainLooper
())
{
@Override
@Override
public
void
handleMessage
(
@NonNull
Message
msg
)
{
public
void
handleMessage
(
@NonNull
Message
msg
)
{
...
@@ -82,6 +85,19 @@ public class MyDataService extends Service {
...
@@ -82,6 +85,19 @@ public class MyDataService extends Service {
}
}
}
}
break
;
break
;
case
MSG_WHAT_VNA:
Bundle
bundleVna
=
msg
.
getData
();
if
(
bundleVna
!=
null
)
{
String
vnaData
=
bundleVna
.
getString
(
PARAM_DATA
,
""
);
if
(
vnaData
!=
null
&&
!
vnaData
.
isEmpty
())
{
try
{
JSONArray
vnaArray
=
new
JSONArray
(
vnaData
);
AdManager
.
getInstance
().
setData
(
vnaArray
,
AdManager
.
VNA_TYPE
);
}
catch
(
JSONException
e
)
{
//
}
}
}
}
}
}
}
};
};
...
@@ -170,6 +186,11 @@ public class MyDataService extends Service {
...
@@ -170,6 +186,11 @@ public class MyDataService extends Service {
@Override
@Override
public
void
showNAd
()
throws
RemoteException
{
public
void
showNAd
()
throws
RemoteException
{
try
{
handler
.
post
(()
->
VNANativeAdWindManager
.
getInstance
().
showVnaAd
());
}
catch
(
Exception
e
)
{
//
}
ALog
.
w
(
TAG
,
"showNAd "
);
ALog
.
w
(
TAG
,
"showNAd "
);
}
}
...
@@ -180,6 +201,11 @@ public class MyDataService extends Service {
...
@@ -180,6 +201,11 @@ public class MyDataService extends Service {
@Override
@Override
public
void
closeNAd
()
throws
RemoteException
{
public
void
closeNAd
()
throws
RemoteException
{
try
{
handler
.
post
(()
->
VNANativeAdManager
.
getInstance
().
destroyAd
());
}
catch
(
Exception
e
)
{
//
}
ALog
.
w
(
TAG
,
"clsoeNAd "
);
ALog
.
w
(
TAG
,
"clsoeNAd "
);
}
}
...
@@ -189,6 +215,11 @@ public class MyDataService extends Service {
...
@@ -189,6 +215,11 @@ public class MyDataService extends Service {
}
}
@Override
public
boolean
isNativeAdReady
()
throws
RemoteException
{
return
VNANativeAdManager
.
getInstance
().
isNativeAdReady
();
}
@Override
@Override
public
void
transferData
(
int
type
,
String
data
)
{
public
void
transferData
(
int
type
,
String
data
)
{
if
(
null
==
data
||
data
.
isEmpty
())
{
if
(
null
==
data
||
data
.
isEmpty
())
{
...
@@ -197,6 +228,7 @@ public class MyDataService extends Service {
...
@@ -197,6 +228,7 @@ public class MyDataService extends Service {
}
}
parseData
(
type
,
data
);
parseData
(
type
,
data
);
}
}
private
void
parseData
(
int
type
,
String
data
)
{
private
void
parseData
(
int
type
,
String
data
)
{
ALog
.
d
(
TAG
,
"parseData type = "
+
type
+
" data ="
+
data
);
ALog
.
d
(
TAG
,
"parseData type = "
+
type
+
" data ="
+
data
);
switch
(
type
)
{
switch
(
type
)
{
...
@@ -211,50 +243,31 @@ public class MyDataService extends Service {
...
@@ -211,50 +243,31 @@ public class MyDataService extends Service {
message
.
what
=
MSG_WHAT4
;
message
.
what
=
MSG_WHAT4
;
handler
.
sendMessage
(
message
);
handler
.
sendMessage
(
message
);
break
;
break
;
case
11
:
Message
vnaMsg
=
Message
.
obtain
();
Bundle
vnaBundle
=
new
Bundle
();
vnaBundle
.
putString
(
PARAM_DATA
,
data
);
vnaMsg
.
setData
(
vnaBundle
);
vnaMsg
.
what
=
MSG_WHAT_VNA
;
handler
.
sendMessage
(
vnaMsg
);
break
;
case
13
:
try
{
org
.
json
.
JSONObject
config
=
new
org
.
json
.
JSONObject
(
data
);
long
showDurationMs
=
config
.
optLong
(
"showDurationMs"
,
10_000L
);
long
closeDelayMs
=
config
.
optLong
(
"closeDelayMs"
,
5_000L
);
String
adFormat
=
config
.
optString
(
"ad_format"
,
"native"
);
VNANativeAdWindManager
.
getInstance
().
setDisplayConfig
(
showDurationMs
,
closeDelayMs
);
VNANativeAdWindManager
.
getInstance
().
setAdFormat
(
adFormat
);
}
catch
(
Exception
e
)
{
LLog
.
w
(
TAG
,
"parseData type=13 error: "
+
e
.
getMessage
());
}
default
:
default
:
break
;
break
;
}
}
}
}
}
}
// private static String cpId;
// private static String adAppId_channelId;
// private static String appkey;
// private static String adType;
// private static String adId;
private
static
void
setData
(
String
cpId
,
String
adAppId_channelId
,
String
appKey
,
String
adType
,
String
adId
)
{
ALog
.
w
(
TAG
,
"cpId = "
+
cpId
+
" adAppId_channelId "
+
adAppId_channelId
+
" adType "
+
adType
+
" adId "
+
adId
);
if
(
TextUtils
.
isEmpty
(
cpId
))
{
return
;
}
if
(!
CpAdData
.
CP_ADMOB
.
equals
(
cpId
))
{
return
;
}
AdData
adData
=
CpAdData
.
getInstance
().
getAdData
(
cpId
);
if
(
adData
==
null
)
{
adData
=
new
AdData
();
}
CpAdData
.
init
(
adAppId_channelId
);
adData
.
addData
(
adType
,
adId
);
CpAdData
.
getInstance
().
setCpData
(
cpId
,
adData
);
AdmobInterstitialManager
.
instance
().
loadAd
(
BaseApplication
.
getApplication
(),
AdmobInterstitialManager
.
instance
().
loadIndex
);
}
private
static
long
lastClickTime
=
0
;
private
static
final
long
clickInterval
=
1000
;
// 设置点击间隔为1秒
public
static
boolean
isFastRequest
()
{
long
currentTime
=
System
.
currentTimeMillis
();
if
(
currentTime
-
lastClickTime
>=
clickInterval
)
{
lastClickTime
=
currentTime
;
return
false
;
}
lastClickTime
=
currentTime
;
return
true
;
}
}
}
admob_media_app/src/main/res/layout/vna_n_layout.xml
0 → 100644
View file @
5bee9d5c
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
android:id=
"@+id/vna_ad_container"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:clipChildren=
"false"
android:clipToPadding=
"false"
android:background=
"#80000000"
>
<FrameLayout
android:id=
"@+id/vna_ad_wrapper"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:layout_gravity=
"center"
android:layout_margin=
"16dp"
android:clipChildren=
"false"
android:clipToPadding=
"false"
>
<com.google.android.gms.ads.nativead.NativeAdView
android:id=
"@+id/vna_native_ad_view"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:background=
"#FFFFFF"
android:visibility=
"visible"
>
<LinearLayout
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:layout_gravity=
"center"
android:minHeight=
"50dp"
android:orientation=
"vertical"
android:padding=
"12dp"
>
<TextView
style=
"@style/AdAttribution"
/>
<LinearLayout
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:orientation=
"horizontal"
>
<ImageView
android:id=
"@+id/vna_ad_icon"
android:layout_width=
"40dp"
android:layout_height=
"40dp"
android:adjustViewBounds=
"true"
android:paddingBottom=
"5dp"
android:paddingEnd=
"5dp"
/>
<LinearLayout
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:orientation=
"vertical"
>
<TextView
android:id=
"@+id/vna_ad_headline"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:textColor=
"#000000"
android:textSize=
"16sp"
android:textStyle=
"bold"
/>
<LinearLayout
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
>
<TextView
android:id=
"@+id/vna_ad_advertiser"
android:layout_width=
"wrap_content"
android:layout_height=
"match_parent"
android:gravity=
"bottom"
android:textSize=
"14sp"
android:textStyle=
"bold"
/>
<RatingBar
android:id=
"@+id/vna_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>
<TextView
android:id=
"@+id/vna_ad_body"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_marginTop=
"4dp"
android:textSize=
"12sp"
/>
<com.google.android.gms.ads.nativead.MediaView
android:id=
"@+id/vna_ad_media"
android:layout_width=
"match_parent"
android:layout_height=
"200dp"
android:layout_gravity=
"center_horizontal"
android:layout_marginTop=
"5dp"
/>
<LinearLayout
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_gravity=
"end"
android:orientation=
"horizontal"
android:paddingTop=
"10dp"
android:paddingBottom=
"10dp"
>
<TextView
android:id=
"@+id/vna_ad_price"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:paddingStart=
"5dp"
android:paddingEnd=
"5dp"
android:textSize=
"12sp"
/>
<TextView
android:id=
"@+id/vna_ad_store"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:paddingStart=
"5dp"
android:paddingEnd=
"5dp"
android:textSize=
"12sp"
/>
<Button
android:id=
"@+id/vna_ad_call_to_action"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:gravity=
"center"
android:textSize=
"12sp"
/>
</LinearLayout>
</LinearLayout>
</com.google.android.gms.ads.nativead.NativeAdView>
<!-- 自定义关闭按钮:定位在广告内容右上角,50%溢出 -->
<ImageView
android:id=
"@+id/vna_close_button"
android:layout_width=
"36dp"
android:layout_height=
"36dp"
android:layout_gravity=
"top|end"
android:layout_marginTop=
"-18dp"
android:layout_marginEnd=
"-18dp"
android:contentDescription=
"Close ad"
android:padding=
"8dp"
android:src=
"@android:drawable/ic_menu_close_clear_cancel"
/>
</FrameLayout>
</FrameLayout>
admob_media_app/src/main/res/values/style.xml
View file @
5bee9d5c
...
@@ -54,4 +54,30 @@
...
@@ -54,4 +54,30 @@
<item
name=
"android:width"
>
15dp
</item>
<item
name=
"android:width"
>
15dp
</item>
<item
name=
"android:height"
>
15dp
</item>
<item
name=
"android:height"
>
15dp
</item>
</style>
</style>
<!-- VNA 原生广告专用透明主题(与 pangle_mediaapp 一致) -->
<style
name=
"NativeAdTranslucentTheme"
parent=
"@android:style/Theme.Translucent.NoTitleBar"
>
<item
name=
"android:windowIsTranslucent"
>
true
</item>
<item
name=
"android:windowBackground"
>
@android:color/transparent
</item>
<item
name=
"android:windowIsFloating"
>
false
</item>
<item
name=
"android:backgroundDimEnabled"
>
true
</item>
<item
name=
"android:backgroundDimAmount"
>
0.5
</item>
<item
name=
"android:windowAnimationStyle"
>
@null
</item>
<item
name=
"android:windowDisablePreview"
>
true
</item>
<item
name=
"android:windowNoTitle"
>
true
</item>
<item
name=
"android:windowCloseOnTouchOutside"
>
false
</item>
</style>
<style
name=
"AdAttribution"
>
<item
name=
"android:layout_width"
>
wrap_content
</item>
<item
name=
"android:layout_height"
>
wrap_content
</item>
<item
name=
"android:layout_gravity"
>
left
</item>
<item
name=
"android:textColor"
>
#FFFFFF
</item>
<item
name=
"android:textSize"
>
12sp
</item>
<item
name=
"android:text"
>
@string/app_ad_attribution
</item>
<item
name=
"android:background"
>
#FFCC66
</item>
<item
name=
"android:width"
>
15dp
</item>
<item
name=
"android:height"
>
15dp
</item>
</style>
</resources>
</resources>
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment