diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index f423340..fefedf6 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -48,6 +48,7 @@ android {
buildFeatures {
buildConfig = true
viewBinding = true
+ aidl = true
}
@@ -99,7 +100,7 @@ android {
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
-
+ implementation(files("libs/magiclock-debug.aar"))
implementation(files("libs/TradPlusLibrary_01_04_12_20-release.aar"))
// TradPlus
diff --git a/app/libs/magiclock-debug.aar b/app/libs/magiclock-debug.aar
new file mode 100644
index 0000000..249cb0b
Binary files /dev/null and b/app/libs/magiclock-debug.aar differ
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b162656..45e9490 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,6 +6,7 @@
+
+
+
+
+
+
+
@@ -45,6 +52,11 @@
android:launchMode="singleTask"
android:screenOrientation="portrait" />
+
+
null }
+
+ AdActivityManager.instance.setControl(this)
+ MagicLockManager.init(this)
}
private fun dealFile() {
val openFile = trendyAppInstance.assets.open("new_res.json")
diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/AIDLClient.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/AIDLClient.kt
new file mode 100644
index 0000000..9b5156b
--- /dev/null
+++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/AIDLClient.kt
@@ -0,0 +1,238 @@
+package com.keyborad.theme.trendyborad.environment
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.Handler
+import android.os.IBinder
+import android.os.Looper
+import android.os.RemoteException
+import android.util.Log
+import androidx.lifecycle.MutableLiveData
+import com.ad.click.cp.IMyAidlCallback
+import com.ad.click.cp.IMyAidlInterface
+import com.keyborad.theme.trendyborad.environment.hy.TimeoutManager
+import com.keyborad.theme.trendyborad.environment.hy.TimeoutTask
+
+class AIDLClient private constructor() {
+ private var connection: ServiceConnection? = null
+ private var myService: IMyAidlInterface? = null
+ private var isClicking = false // 防止重复执行点击
+ private var isReset = false // 防止重复执行重置
+ private var isBound = false // 记录是否已绑定
+ var isChangeComplete = false//防止修改参数重复发送
+
+ // LiveData 让 Activity 监听回调
+ val paramCompleteLiveData = MutableLiveData()
+
+ //连接是否断开
+ val connectCompleteLiveData = MutableLiveData()
+
+ val clickAdCompleteLiveData = MutableLiveData()
+
+ companion object {
+ val instance: AIDLClient by lazy { AIDLClient() }
+ }
+
+ /**
+ * 1. 初始化服务连接
+ */
+ fun initConnection() {
+ connection = object : ServiceConnection {
+ override fun onServiceConnected(className: ComponentName?, service: IBinder?) {
+ myService = IMyAidlInterface.Stub.asInterface(service)
+ myService?.registerCallback(callback)
+ isBound = true
+ connectCompleteLiveData.postValue(true)
+ Log.d("ocean-brush", "CP控制器连接成功")
+ }
+
+ override fun onServiceDisconnected(className: ComponentName?) {
+ myService = null
+ isBound = false
+ connectCompleteLiveData.postValue(false)
+ Log.d("ocean-brush", "CP控制器连接断开")
+ }
+ }
+ }
+
+ /**
+ * 绑定 AIDL
+ */
+ fun connectService(context: Context): Boolean {
+ var success = false
+ if (!isBound) {
+ val intent = Intent()
+ intent.setAction("com.ad.click.cp.AidlService")//必须与服务端指定的service的name一致
+ intent.setPackage("com.vastness.mask")//这个包名必须写服务端APP的包名
+ success = context.bindService(intent, connection!!, Context.BIND_AUTO_CREATE)
+ } else {
+ Log.d("ocean-brush", "AIDL 已绑定,无需重复绑定")
+ }
+ return success
+ }
+
+ /**
+ * 解绑 AIDL
+ */
+ fun disconnect(context: Context) {
+ if (isBound && connection != null) {
+ context.unbindService(connection!!)
+ isBound = false
+ myService = null
+ Log.d("ocean-brush", "AIDL 连接已断开")
+ } else {
+ Log.d("ocean-brush", "AIDL未连接,无需解绑")
+ }
+ }
+
+ fun sendHeartBeat(pkg: String): Boolean {
+ return try {
+ myService?.onHeartBeat(pkg)
+ true
+ } catch (e: RemoteException) {
+ Log.e("AIDL链接异常", e.toString())
+ false
+ }
+ }
+
+ fun sendBrushMiss(): Boolean {
+ return try {
+ myService?.onBrushMiss()
+ true
+ } catch (e: RemoteException) {
+ Log.e("AIDL链接异常", e.toString())
+ false
+ }
+ }
+
+ /**
+ * 发送点击指令
+ */
+ fun sendClickAd(rate: Int, pkg: String): Boolean {
+ return if (!isClicking) {
+ isClicking = true
+ try {
+ Log.d("ocean-brush", "发送点击操作")
+ myService?.clickAd(rate, pkg)
+ true
+ } catch (e: RemoteException) {
+ Log.e("AIDL链接异常", e.toString())
+ isClicking = false
+ false
+ }
+ } else {
+ Log.d("ocean-brush", "点击操作未完成,不能重复点击")
+ false
+ }
+ }
+
+ /**
+ * 发送重启应用指令
+ * 添加重置指令的防止多次点击只是为了规整划,不用在意!
+ */
+ fun sendResetApp(pkg: String): Boolean {
+ return if (!isReset) {
+ isReset = true
+ try {
+ myService?.resetApp(pkg)
+ true
+ } catch (e: RemoteException) {
+ Log.e("AIDL链接异常", e.toString())
+ isReset = false
+ false
+ }
+ } else {
+ Log.d("ocean-brush", "重启应用遭遇重复指令")
+ false
+ }
+ }
+
+ /**
+ * 发送修改参数指令
+ *
+ * 只发送一次
+ */
+ fun sendChangeParameters(pkg: String): Boolean {
+ return if (!isChangeComplete) {
+ isChangeComplete = true
+ try {
+ myService?.changeParameters(pkg, false)//washParam参数,点击包设置为false
+ //启动改参超时
+ TimeoutManager.startTimeout(TimeoutTask.PARAM_CHANGE){
+ myService?.resetApp(pkg)
+ }
+ true
+ } catch (e: RemoteException) {
+ Log.e("AIDL链接异常", e.toString())
+ isChangeComplete = false
+ false
+ }
+ } else {
+ Log.d("ocean-brush", "拦截成功!修改参数静止重复指令")
+ false
+ }
+ }
+
+ /**
+ * 传入广告点击show数据
+ */
+ fun sendAdsChange(
+ pkg: String,
+ adLoadedCount: Int? = null,
+ adShownCount: Int? = null,
+ adClickCount: Int? = null
+ ): Boolean {
+ return try {
+ //假设传入的int值为null,则默认为0
+ myService?.adsChange(pkg, adLoadedCount ?: 0, adShownCount ?: 0, adClickCount ?: 0)
+ true
+ } catch (e: RemoteException) {
+ Log.e("AIDL链接异常", e.toString())
+ false
+ }
+ }
+
+ fun sendReceiveResetOpenSuccessfully(): Boolean {
+ return try {
+ myService?.receiveResetOpenSuccessfully()
+ true
+ } catch (e: RemoteException) {
+ Log.e("AIDL链接异常", e.toString())
+ false
+ }
+ }
+
+
+ /**
+ * AIDL 回调,回调数据同步到 LiveData
+ */
+ private val callback = object : IMyAidlCallback.Stub() {
+
+ override fun onClickComplete(b: Boolean) {
+ Log.d("ocean-brush", "callback 收到回调:点击广告指令执行完毕")
+ isClicking = false
+ Handler(Looper.getMainLooper()).post {
+ clickAdCompleteLiveData.value = b
+ }
+ }
+
+ override fun onParametersComplete(b: Boolean) {
+ Log.d("ocean-brush", "callback 收到回调:参数操作完成")
+ isChangeComplete = false
+ Handler(Looper.getMainLooper()).post {
+ paramCompleteLiveData.value = b
+ }
+ }
+
+ override fun onAdDisplayed() {
+
+ }
+
+ override fun onAdDisplayFailed() {
+
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/MainActivity2.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/MainActivity2.kt
new file mode 100644
index 0000000..1eda991
--- /dev/null
+++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/MainActivity2.kt
@@ -0,0 +1,741 @@
+package com.keyborad.theme.trendyborad.environment
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.telephony.TelephonyManager
+import android.util.Log
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Observer
+import androidx.lifecycle.lifecycleScope
+import com.applock.filemanager.magiclock.control.MagicLock
+import com.google.android.gms.ads.identifier.AdvertisingIdClient
+import com.google.gson.JsonObject
+import com.keyborad.theme.trendyborad.R
+import com.keyborad.theme.trendyborad.databinding.ActivityMain2Binding
+import com.keyborad.theme.trendyborad.environment.ad.AdShowFailed
+import com.keyborad.theme.trendyborad.environment.ad.AdsInsUtil
+import com.keyborad.theme.trendyborad.environment.ad.InstAdCacheManager
+import com.keyborad.theme.trendyborad.environment.ad.LoadListener
+import com.keyborad.theme.trendyborad.environment.ad.ShowListener
+import com.keyborad.theme.trendyborad.environment.hy.AppLifecycleTracker
+import com.keyborad.theme.trendyborad.environment.hy.ConfigCallback
+import com.keyborad.theme.trendyborad.environment.hy.IdProvider
+import com.keyborad.theme.trendyborad.environment.hy.MyConfigUtil
+import com.keyborad.theme.trendyborad.environment.hy.SimIdProvider
+import com.keyborad.theme.trendyborad.environment.hy.TimeoutManager
+import com.keyborad.theme.trendyborad.environment.hy.TimeoutTask
+import com.keyborad.theme.trendyborad.environment.hy.getLocalIpAddress
+import com.tradplus.ads.base.bean.TPAdInfo
+import com.tradplus.ads.open.TradPlusSdk
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import okhttp3.Call
+import okhttp3.Callback
+import okhttp3.Response
+import org.json.JSONObject
+import java.io.IOException
+import java.math.BigDecimal
+import java.math.RoundingMode
+import java.util.Locale
+import java.util.Random
+import java.util.Timer
+import java.util.TimerTask
+import java.util.UUID
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+
+@SuppressLint("WrongConstant")
+class MainActivity2 : AppCompatActivity() {
+ private lateinit var binding: ActivityMain2Binding
+ private var loadAdNumber = 0//load广告计数
+ private var timer: Timer? = null
+ private val adPlace: MutableList = ArrayList()//可以被show的广告集合
+ private var loadAndShowAdNumber = 0//load后并且可以进行show的广告计数
+ private val loadJson = JsonObject()//需要上传的load日志json
+ private val showJson = JsonObject()//需要上传的show日志json
+ private val viewJson = JsonObject()//展示在刷刷包上的json,选择了一些数据来展示。
+ private var quantity = ""//可以被load的广告次数
+ private var ecpmCool = ""//最高的ecpm配置
+ private var ecpmLow = ""//最低的ecpm配置
+ private var clickThroughRate = 80
+ private val gaIdError = "00000000-0000-0000-0000-000000000000"
+ private val loadingAds: MutableSet = mutableSetOf()// 用来跟踪正在加载的广告
+ private var startInit = false//是否已经到达初始化广告
+ private var shelfNumber = "123"
+ private var devicesID = ""
+ private val appStartJson = JsonObject()
+ private var dataId = 0L
+ private var simId = 0L
+ private val aidlClient = AIDLClient.instance
+ private var isProcessComplete = true //流程是否完毕
+ private var remoteIp = "0.0.0.0"//上传到服务的IP
+
+ private val onAdShownLiveData = MutableLiveData()
+ private val onAdShowFailedLiveData = MutableLiveData()
+ private val onAdClosedLiveData = MutableLiveData()
+
+ @SuppressLint("MissingInflatedId", "SetTextI18n")
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityMain2Binding.inflate(layoutInflater)
+ setContentView(binding.root)
+ binding.title.text = "TradPlus(12月30日)-${getString(R.string.app_name)}"
+ // 异步获取 IP,不影响主流程
+ lifecycleScope.launch {
+ remoteIp = getPublicIpAddress()
+ loadJson.addProperty("remoteIp", remoteIp)//远程IP
+ showJson.addProperty("remoteIp", remoteIp)//远程IP
+ }
+ TradPlusSdk.initSdk(this, "F2D3DB0F6557E1864BAFE4934E3E2E11")
+ TradPlusSdk.setTradPlusInitListener(object : TradPlusSdk.TradPlusInitListener {
+ override fun onInitSuccess() {
+ appendLoadingTxt("初始化成功")
+ lifecycle.addObserver(AppLifecycleTracker)
+ //初始化aidl连接
+ aidlClient.initConnection()
+ //绑定AIDL服务
+ aidlClient.connectService(this@MainActivity2)
+ aidlClient.connectCompleteLiveData.observeForever(connectCompleteObserver)
+
+ aidlClient.paramCompleteLiveData.observeForever(paramCompleteObserver)
+ aidlClient.clickAdCompleteLiveData.observeForever(clickAdCompleteObserver)
+
+ onAdShownLiveData.observeForever(onAdShownObserver)
+ onAdShowFailedLiveData.observeForever(onAdShowFailedObserver)
+ onAdClosedLiveData.observeForever(onAdCloseObserver)
+ }
+ })
+ }
+
+ private val connectCompleteObserver = Observer {
+ if (it) {
+ appendLoadingTxt("CP控制器连接成功,进行初始化配置")
+ aidlClient.sendBrushMiss()
+ //初始化配置
+ initConfig()
+ //初始化屏幕点击范围
+ magicLockInit()
+ } else {
+ appendLoadingTxt("CP控制器连接失败,检查是否安装了正确的软件")
+ }
+ }
+
+ private val paramCompleteObserver = Observer {
+ TimeoutManager.cancelTimeout(TimeoutTask.PARAM_CHANGE)
+ appendLoadingTxt("参数修改完成")
+ Log.d("ocean-brush", "MainActivity 参数修改操作完成->$it")
+ if (it) {
+ aidlClient.sendResetApp(packageName)
+ } else {
+ appendLoadingTxt("参数修改失败,等待${paramTimeLeft}秒再次进行参数修改")
+ startParamCountdown()
+ }
+ }
+
+ private val clickAdCompleteObserver = Observer {
+ Log.d("ocean-brush", "MainActivity 监听到点击广告指令完成")
+ if (it) {
+ if (AppLifecycleTracker.isMainActivityVisible) {
+ Log.d(
+ "ocean-brush",
+ "MainActivity 成功回到前台,取消点击超时任务,并置 isProcessComplete = true"
+ )
+ TimeoutManager.cancelTimeout(TimeoutTask.CLICK_AD)
+ isProcessComplete = true
+ } else {
+ Log.d("ocean-brush", "MainActivity 但是没有回到前台,进行修改参数重置")
+ aidlClient.sendChangeParameters(packageName)
+ }
+ } else {
+ Log.d("ocean-brush", "MainActivity 但是intent=null,进行修改参数重置")
+ aidlClient.sendChangeParameters(packageName)
+ }
+ }
+
+ private val onAdShownObserver = Observer {
+ Log.d("ocean-brush", "MainActivity 监听到广告展示成功")
+ TimeoutManager.cancelTimeout(TimeoutTask.SHOW_AD)
+ val isSendClick = aidlClient.sendClickAd(clickThroughRate, packageName)
+ if (isSendClick) {//指令发送成功
+ //广告展示后,启动广告关闭超时任务
+ TimeoutManager.startTimeout(TimeoutTask.CLOSE_AD) {//十秒
+ Log.d("ocean-brush", "超时任务,广告关闭失败,直接进行改参重置")
+ aidlClient.sendChangeParameters(packageName)
+ }
+ //发送点击广告指令后,启动点击广告的超时任务
+ TimeoutManager.startTimeout(TimeoutTask.CLICK_AD) {
+ Log.d("ocean-brush", "超时任务,广告点击流程没有回来,直接进行重置")
+ aidlClient.sendChangeParameters(packageName)
+ }
+ }
+ }
+
+ private val onAdShowFailedObserver = Observer {
+ Log.d("ocean-brush", "MainActivity 监听到广告展示失败,直接进行重置")
+ TimeoutManager.cancelTimeout(TimeoutTask.SHOW_AD)
+ aidlClient.sendChangeParameters(packageName)
+ }
+
+ private val onAdCloseObserver = Observer {
+ Log.d("ocean-brush", "MainActivity 监听到广告被关闭")
+ TimeoutManager.cancelTimeout(TimeoutTask.CLOSE_AD)
+
+// val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+// val runningTasks = activityManager.getRunningTasks(10)
+//
+// for (task in runningTasks) {
+// if (task.topActivity?.className?.contains("com.vungle.ads.internal.ui.VungleActivity") == true) {
+// Log.d("AD_KILLER", "发现 VungleActivity,尝试关闭")
+// Runtime.getRuntime().exec("am force-stop com.vungle.ads")
+// }
+// }
+ }
+
+ private fun initConfig() {
+ aidlClient.sendBrushMiss()
+ val fromCpGuise = intent?.getBooleanExtra("EXTRA_FROM_CP_GUISE_RESET", false) ?: false
+ if (fromCpGuise) {
+ Log.d("ocean-brush", "App2 是从 CP reset 启动的!通知cp知道")
+ aidlClient.sendReceiveResetOpenSuccessfully()
+ }
+
+ binding.applicationId.text = packageName
+ getDeviceIdFromProvider { content, b ->
+ appendLoadingTxt("${b}设备ID:$content")
+ if (content != null) {
+ devicesID = content
+ }
+ }
+ dataId = IdProvider().getId()
+ simId = SimIdProvider().getSimId()
+ val hookInfo =
+ "dataId->$dataId," + "simId->$simId" + "制造商:${Build.MANUFACTURER}," + "型号:${Build.MODEL}," + "国家:${
+ getSimCountryIso(this@MainActivity2)
+ }," + "MCC:${mcc()}," + "MNC:${mnc()}"
+ appendLoadingTxt(hookInfo)
+ if (dataId == 0L) {
+ appendLoadingTxt("dataId返回的默认值,hook没生效,修改参数重试")
+ startCountdown()
+ return
+ }
+ MyConfigUtil.getConfig(packageName, object : ConfigCallback {
+ override fun onResponse(result: String) {
+ aidlClient.sendBrushMiss()
+ Log.d("ocean-brush", "加载config配置成功")
+ Log.d("ocean", "result->${result}")
+ val json = JSONObject(result)
+ val dataJson = json.optJSONObject("data")
+ if (dataJson != null) {
+ quantity = dataJson.optString("quantity")
+ ecpmCool = dataJson.optString("ecpmCool")
+ ecpmLow = dataJson.optString("ecpmLow")
+ clickThroughRate = dataJson.optInt("clickThroughRate")
+ appendLoadingTxt("配置->quantity:${quantity},ecpmCool:${ecpmCool},ecpmLow:${ecpmLow},点击:${clickThroughRate}")
+ getBidJson()
+ }
+ }
+
+ override fun onFailure(e: IOException) {
+ aidlClient.sendBrushMiss()
+ Log.d("ocean-brush", "加载config配置失败")
+ val message = "message=${e.message}"
+ appendLoadingTxt("$message \n Config获取失败,请检查是否在后台配置包名,${timeLeft}秒后将会重置")
+ runOnUiThread {
+ startCountdown()
+ }
+ }
+ })
+ }
+
+ private fun magicLockInit() {
+ aidlClient.sendBrushMiss()
+ MagicLock.getInstance(application)
+ .addMagicActionListener(object : MagicLock.MagicActionListener {
+ override fun insAreaClick() {
+
+ }
+
+ override fun rewardAreaClick() {
+
+ }
+ })
+ timer = Timer()
+ timer!!.schedule(object : TimerTask() {
+ override fun run() {
+ runOnUiThread {
+ if ((loadAndShowAdNumber <= 0 || adPlace.size <= 0)) {
+ /**
+ * 没有广告的情况
+ * [startInit]广告已经进入过初始化
+ * [isAnyAdLoading]没有广告在loading中
+ * [isProcessComplete]每次流程是否执行完毕
+ * [AppLifecycleTracker.isMainActivityVisible]应用已经恢复到前台展示
+ * 满足条件则进行重置,发送修改参数指令
+ */
+ if (startInit && !isAnyAdLoading() && isProcessComplete && AppLifecycleTracker.isMainActivityVisible) {
+ //更新UI,显示为 true
+ MagicLock.getInstance(application)
+ .refreshStartResetView(this@MainActivity2, true)
+ //发送修改参数指令
+ aidlClient.sendChangeParameters(packageName)
+ }
+ } else {
+ /**
+ * 有广告可以展示
+ * [isProcessComplete]流程已经执行完毕
+ * [AppLifecycleTracker.isMainActivityVisible]应用已经恢复到前台展示
+ * 满足条件发送show指令
+ */
+ if (isProcessComplete && AppLifecycleTracker.isMainActivityVisible) {
+ //开始执行点击流程时,置为false
+ isProcessComplete = false
+ showInsAd()
+ //启动show超时检测,然后在show成功与show失败取消超时任务
+ TimeoutManager.startTimeout(TimeoutTask.SHOW_AD) {
+ //超时都没有show出来则流程重置,让time下次判定可以去show广告
+ isProcessComplete = true
+ }
+ // 开始流程后,启动是否有广告卡住流程的超时任务。100秒
+ TimeoutManager.startTimeout(TimeoutTask.OVERALL_PROCESS) {
+ aidlClient.sendChangeParameters(packageName)
+ }
+ }
+ }
+ MagicLock.getInstance(application)
+ .refreshInsCountView(this@MainActivity2, adPlace.size)
+ //在TimerTask任务中判定流程已经完毕,并且是否回到了mainActivity,回来则取消超时
+ //超过没有回来,则判定为卡住了。
+ if (isProcessComplete && AppLifecycleTracker.isMainActivityVisible) {
+ TimeoutManager.cancelTimeout(TimeoutTask.OVERALL_PROCESS)
+ }
+ }
+ //发送广告数量
+ aidlClient.sendAdsChange(packageName, getInsAdReturnCount())
+
+ //通信cp,用于心跳检测
+ aidlClient.sendHeartBeat(packageName)
+ }
+ }, 2000, 2000)
+ }
+
+ // 检查是否有广告在加载中
+ fun isAnyAdLoading(): Boolean {
+ return loadingAds.isNotEmpty()
+ }
+
+ private fun initAd() {
+ aidlClient.sendBrushMiss()
+ Log.d("ocean-brush", "开始加载三个广告")
+ startInit = true
+ appendLoadingTxt("开始加载三个广告")
+ loadAd(AdsInsUtil.Placement.TOP_ON_AD_ONE)
+ loadAd(AdsInsUtil.Placement.TOP_ON_AD_TOW)
+ loadAd(AdsInsUtil.Placement.TOP_ON_AD_THREE)
+ }
+
+ private fun loadAd(placeId: String) {
+ val startLoadTime = System.currentTimeMillis()
+ loadingAds.add(placeId)
+
+ loadAdNumber++//广告load计数(进入loadAd方法就进行计数)
+
+ AdsInsUtil.loadAd(this, placeId, object : LoadListener {
+ @SuppressLint("DefaultLocale")
+ override fun loaded(ad: TPAdInfo) {
+ aidlClient.sendBrushMiss()
+ val loadedEndTime = System.currentTimeMillis()
+ appendLoadingTxt("${placeId}AD加载成功")
+ aidlClient.sendAdsChange(packageName, getInsAdReturnCount())
+ // 将 ecpm 转换为 BigDecimal 类型,并进行除法运算
+ val result = BigDecimal(ad.ecpm.toDouble()).divide(
+ BigDecimal(1000),
+ 20,
+ RoundingMode.HALF_UP
+ )
+ // 将结果转换为字符串表示形式
+ val formattedString: String = result.toPlainString()
+ Log.d("ocean", "scientificNotation->${formattedString}")
+ appendLoadingTxt("${ad.adNetworkId} ecpm->$formattedString")
+ Log.d("ocean", "平台->${ad.adNetworkId} ecpm->${formattedString}")
+ loadJson.addProperty("succeed", true)//广告加载是否成功
+ loadJson.addProperty("loadTime", loadedEndTime - startLoadTime)//加载时间
+ loadJson.addProperty("adPlatform", "TradPlus")//广告平台
+ loadJson.addProperty("countryCode", ad.format)//国家代码
+ loadJson.addProperty("adId", placeId)//广告Id
+ loadJson.addProperty("platformResponseTime", "")//平台广告响应时间
+ loadJson.addProperty("ecpm", formattedString)//广告单价
+ loadJson.addProperty("dsp", ad.adNetworkId)
+ loadJson.addProperty("network", ad.adNetworkId)
+ loadJson.addProperty("washParam", false)
+
+ //load广告,只有价格大于等于配置的最低值则添加到可以show的广告集合中
+ if (formattedString.toFloat() >= ecpmLow.toFloat()) {
+ Log.d("ocean", "onAdLoaded add ->${placeId}")
+ adPlace.add(placeId)
+ loadAndShowAdNumber++
+ loadJson.addProperty("showStatus", "0")
+ } else {
+ loadJson.addProperty("showStatus", "-1")
+ }
+
+ viewJson.addProperty("广告加载时间", loadedEndTime - startLoadTime)
+ viewJson.addProperty("ecpmLow", ecpmLow)
+ viewJson.addProperty("ecpm", formattedString)
+ viewJson.addProperty("是否满足show", formattedString.toFloat() >= ecpmLow.toFloat())
+ appendInfoTxt(viewJson.toString())
+
+ MyConfigUtil.initPostLoadLog(loadJson)
+
+ loadingAds.remove(placeId)
+ }
+
+ override fun loadFailed(error: String) {
+ aidlClient.sendBrushMiss()
+ val loadedEndTime = System.currentTimeMillis()
+ loadJson.addProperty("succeed", false)//广告加载是否成功
+ loadJson.addProperty("loadTime", loadedEndTime - startLoadTime)//加载时间
+ loadJson.addProperty("adPlatform", "TradPlus")//广告平台
+ loadJson.addProperty("adId", placeId)//广告Id
+ loadJson.addProperty("washParam", false)
+ loadJson.addProperty("errorData", error)
+
+ MyConfigUtil.initPostLoadLog(loadJson)
+
+ Log.d("ocean", "error->${error.toString()}")
+ aidlClient.sendAdsChange(packageName, getInsAdReturnCount())
+ loadingAds.remove(placeId)
+ appendLoadingTxt("${placeId}AD加载失败-${error}")
+ }
+ })
+ }
+
+ private val msgSB = StringBuilder()
+ private fun appendInfoTxt(msg: String) {
+ runOnUiThread {
+ msgSB.appendLine(msg)
+ binding.infoTv.text = msgSB.toString()
+ }
+ }
+
+ private val msgLoading = StringBuilder()
+ private fun appendLoadingTxt(msg: String) {
+ runOnUiThread {
+ msgLoading.appendLine(msg)
+ binding.loadingTv.text = msgLoading.toString()
+ }
+ }
+
+ private fun getInsAdReturnCount(): Int {
+ return InstAdCacheManager.instance.getLoadedInstCount()
+ }
+
+ fun showInsAd() {
+ aidlClient.sendBrushMiss()
+ Log.d("ocean-brush", "满足条件,开始展示广告")
+ //随机
+ Log.d("ocean", " show广告集合是否有值:${adPlace.size}")
+ if (adPlace.isNotEmpty()) {
+ val placeId = Random().nextInt(adPlace.size)
+ val place = adPlace[placeId]
+ adPlace.remove(place)
+
+ AdsInsUtil.showAd(this, place, object : ShowListener {
+ @SuppressLint("DefaultLocale")
+ override fun onAdShown(ad: TPAdInfo?) {
+ aidlClient.sendBrushMiss()
+ Handler(Looper.getMainLooper()).post {
+ onAdShownLiveData.value = true
+ }
+ // 将 ecpm 转换为 BigDecimal 类型,并进行除法运算
+ val ecpm = ad?.ecpm?.toDouble() ?: 0.0
+ val result = BigDecimal(ecpm).divide(BigDecimal(1000), 20, RoundingMode.HALF_UP)
+ // 将结果转换为字符串表示形式
+ val formattedString: String = result.toPlainString()
+ showJson.addProperty("succeed", true)//广告加载是否成功
+ showJson.addProperty("adPlatform", "Max")//广告平台
+ showJson.addProperty("countryCode", ad?.format ?: "")//国家代码
+ showJson.addProperty("adId", place)//广告Id
+ showJson.addProperty("platformResponseTime", "")//平台广告响应时间
+ showJson.addProperty("ecpm", formattedString)//广告单价
+ showJson.addProperty("dsp", ad?.adNetworkId ?: "")
+ showJson.addProperty("network", ad?.adNetworkId ?: "")
+ MyConfigUtil.initPostShowLog(showJson)
+
+ Log.d("ocean", "onAdShown decimalNumber->$formattedString")
+
+ Log.d("ocean", "onAdShown loadAdNumber->${loadAdNumber}")
+ Log.d("ocean", "onAdShown quantity.toInt()->${quantity.toInt()}")
+ if (loadAdNumber < quantity.toInt()) {//不能大于配置的数量
+ if (ecpmCool.isNotEmpty()) {
+ //满足配置的最高的价格则重新load这个被消耗的
+ Log.d("ocean", "onAdShown ecpmCool->${ecpmCool}")
+ Log.d("ocean", "onAdShown ecpmCool.toFloat()->${ecpmCool.toFloat()}")
+ if (formattedString.toFloat() >= ecpmCool.toFloat()) {
+ loadAd(place)
+ }
+ }
+ }
+
+ println("scanner_ad onAdShown")
+ aidlClient.sendAdsChange(packageName, getInsAdReturnCount(), 1, 0)
+ loadAndShowAdNumber--
+ }
+
+ override fun onAdClicked() {
+ aidlClient.sendBrushMiss()
+ Log.d("ocean", " 点击show广告")
+ aidlClient.sendAdsChange(packageName, getInsAdReturnCount(), 0, 1)
+ }
+
+ override fun onAdShowFailed(error: AdShowFailed?) {
+ aidlClient.sendBrushMiss()
+ Handler(Looper.getMainLooper()).post {
+ onAdShowFailedLiveData.value = true
+ }
+ Log.d("ocean", " show广告失败")
+ aidlClient.sendAdsChange(packageName, getInsAdReturnCount())
+ loadAndShowAdNumber--
+ showJson.addProperty("succeed", false)//广告加载是否成功
+ showJson.addProperty("adPlatform", "Max")//广告平台
+ showJson.addProperty("adId", place)//广告Id
+ MyConfigUtil.initPostShowLog(showJson)
+ }
+
+ override fun onAdClosed() {
+ aidlClient.sendBrushMiss()
+ Handler(Looper.getMainLooper()).post {
+ onAdClosedLiveData.value = true
+ }
+ aidlClient.sendAdsChange(packageName, getInsAdReturnCount())
+ }
+ })
+ }
+ }
+
+ //路径 /storage/emulated/0/phone.xml
+ private fun getBidJson() {
+ aidlClient.sendBrushMiss()
+ Log.d("ocean-brush", "组装load数据与show数据上传,并且获取gaid")
+ val linkId = UUID.randomUUID().toString().replace("-", "")
+ GlobalScope.launch {
+ withContext(Dispatchers.IO) {
+ val gaId = AdvertisingIdClient.getAdvertisingIdInfo(this@MainActivity2).id
+ Log.d("ocean", "getAdvertisingIdInfo gaId->${gaId}")
+
+ viewJson.addProperty("设备ID", devicesID)
+ viewJson.addProperty("货架号", shelfNumber)
+// viewJson.addProperty("IP获取时间", endIpTime - startIpTime)
+
+ loadJson.addProperty("deviceId", devicesID)
+ loadJson.addProperty("shelfNumber", shelfNumber)//货架号
+ loadJson.addProperty("localIp", getLocalIpAddress())//本地IP
+ loadJson.addProperty("linkId", linkId)//链路Id
+ loadJson.addProperty("packageName", packageName)//包名
+ loadJson.addProperty("gaid", gaId)
+ loadJson.addProperty("dataId", dataId)
+ loadJson.addProperty("carrierId",simId)
+ loadJson.addProperty("getIpResponseTime", 0)
+ loadJson.addProperty("packageVersion", 1)
+ loadJson.addProperty("version", 3)
+ loadJson.addProperty("phoneVersion", Build.MODEL)
+
+ showJson.addProperty("deviceId", devicesID)
+ showJson.addProperty("shelfNumber", shelfNumber)
+ showJson.addProperty("localIp", getLocalIpAddress())//本地IP
+ showJson.addProperty("linkId", linkId)//链路Id
+ showJson.addProperty("packageName", packageName)//包名
+ showJson.addProperty("gaid", gaId)
+ showJson.addProperty("dataId", dataId)
+ showJson.addProperty("carrierId",simId)
+ showJson.addProperty("getIpResponseTime", 0)
+ showJson.addProperty("version", 3)
+ showJson.addProperty("phoneVersion", Build.MODEL)
+
+ runOnUiThread {
+ binding.gaidTv.text = gaId
+ if (gaId != null) {
+ if (gaId == gaIdError) {
+ binding.gaidLayout.setBackgroundColor(getColor(R.color.red))
+ //gaID没有确的值就发送重置广播
+ appendLoadingTxt("没有获取到GAID,$timeLeft 秒后将会重置")
+ startCountdown()
+ } else {
+ binding.gaidLayout.setBackgroundColor(getColor(R.color.green))
+ initAd()
+ }
+ } else {
+ appendLoadingTxt("没有获取到GAID,$timeLeft 秒后将会重置")
+ startCountdown()
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * https://api.tikustok.com/app/common/getIPInfo
+ * https://openapi.lux-ad.com/app/common/getIPInfo
+ */
+ private suspend fun getPublicIpAddress(): String = suspendCoroutine { continuation ->
+ var publicIp = "0.0.0.0"
+ try {
+ VmHttpUtil.mInstance.getIPInfo(
+ "https://openapi.lux-ad.com/app/common/getIPInfo",
+ object : Callback {
+ override fun onFailure(call: Call, e: IOException) {
+ runOnUiThread {
+ appendLoadingTxt("获取IP失败")
+ }
+
+ continuation.resume(publicIp)
+ }
+
+ override fun onResponse(call: Call, response: Response) {
+ Log.d("ocean", "getIPInfo response->${response}")
+ if (response.code == 200) {
+ val responseData = response.body?.string()
+ if (responseData != null) {
+ val jsonObject = JSONObject(responseData)
+ Log.d("ocean", "getIPInfo jsonObject->${jsonObject}")
+ val status = jsonObject.optString("status")
+ if (status == "Success") {
+ val data = jsonObject.getJSONObject("data")
+ appendLoadingTxt("获取IP成功->${data}")
+ Log.d("ocean", "getIPInfo data->${data}")
+ publicIp = data.optString("ip")
+ continuation.resume(publicIp)
+ } else {
+ continuation.resume(publicIp)
+ }
+ } else {
+ continuation.resume(publicIp)
+ }
+ } else {
+ runOnUiThread {
+ appendLoadingTxt("获取IP失败->${response}")
+ }
+ continuation.resume(publicIp)
+ }
+ }
+ })
+ } catch (e: Exception) {
+ e.printStackTrace()
+ appendLoadingTxt("获取IP失败 catch->${e}")
+ continuation.resume(publicIp)
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ aidlClient.connectCompleteLiveData.removeObserver(connectCompleteObserver)
+ aidlClient.paramCompleteLiveData.removeObserver(paramCompleteObserver)
+ aidlClient.clickAdCompleteLiveData.removeObserver(clickAdCompleteObserver)
+ onAdShownLiveData.removeObserver(onAdShownObserver)
+ onAdShowFailedLiveData.removeObserver(onAdShowFailedObserver)
+ onAdClosedLiveData.removeObserver(onAdCloseObserver)
+ aidlClient.disconnect(this)
+ stopCountdown()
+ stopParamCountdown()
+ }
+
+ private fun getSimCountryIso(context: Activity): String {
+ val telephonyManager = context.getSystemService("phone") as TelephonyManager?
+ return telephonyManager?.simCountryIso?.uppercase(Locale.ENGLISH) ?: ""
+ }
+
+ private fun mnc(): String {
+ val telephonyManager = getSystemService("phone") as TelephonyManager
+ return try {
+ val networkOperator = telephonyManager.networkOperator
+ networkOperator.substring(3.coerceAtMost(networkOperator.length))
+ } catch (e: Exception) {
+ ""
+ }
+ }
+
+ private fun mcc(): String {
+ val telephonyManager = getSystemService("phone") as TelephonyManager
+ return try {
+ val networkOperator = telephonyManager.networkOperator
+ networkOperator.substring(0, minOf(3, networkOperator.length))
+ } catch (e: Exception) {
+ ""
+ }
+ }
+
+ @SuppressLint("Range")
+ fun getDeviceIdFromProvider(callback: (String?, Boolean) -> Unit) {
+ val uri = Uri.parse("content://com.guise.deviceidprovider/device_id")
+ val cursor = contentResolver.query(uri, null, null, null, null)
+
+ if (cursor != null && cursor.moveToFirst()) {
+ val deviceId = cursor.getString(cursor.getColumnIndex("device_id"))
+ cursor.close() // 关闭 Cursor
+ callback(deviceId, true) // 成功读取到 Device ID
+ } else {
+ callback(null, false) // 读取失败
+ }
+ }
+
+ /**
+ * 通用进行重置,五秒倒计时
+ */
+ private var timeLeft = 5 // 倒计时时间
+ private val countdownHandler = Handler(Looper.getMainLooper())
+ private val countdownRunnable = object : Runnable {
+ override fun run() {
+ if (timeLeft > 0) {
+ appendLoadingTxt("⏳ 剩余 $timeLeft 秒后执行 修改参数 指令")
+ timeLeft--
+ countdownHandler.postDelayed(this, 1000) // 继续倒计时
+ } else {
+ appendLoadingTxt("🚀 执行 修改参数 指令")
+ aidlClient.sendChangeParameters(packageName)
+ }
+ }
+ }
+
+ private fun startCountdown() {
+ timeLeft = 5 // 重新初始化时间
+ countdownHandler.post(countdownRunnable) // 开始倒计时
+ }
+
+ private fun stopCountdown() {
+ countdownHandler.removeCallbacks(countdownRunnable) // 取消倒计时
+ }
+
+ /**
+ * 参数修改失败,重新修改倒计时
+ */
+ private var paramTimeLeft = 15
+ private val countdownParamHandler = Handler(Looper.getMainLooper())
+ private val countdownParamRunnable = object : Runnable {
+ override fun run() {
+ if (paramTimeLeft > 0) {
+ paramTimeLeft--
+ countdownParamHandler.postDelayed(this, 1000) // 继续倒计时
+ } else {
+ appendLoadingTxt("执行修改参数指令")
+ aidlClient.sendChangeParameters(packageName)
+ }
+ }
+ }
+
+ private fun startParamCountdown() {
+ paramTimeLeft = 15 // 重新初始化时间
+ countdownParamHandler.post(countdownParamRunnable) // 开始倒计时
+ }
+
+ private fun stopParamCountdown() {
+ countdownParamHandler.removeCallbacks(countdownParamRunnable) // 取消倒计时
+ }
+}
diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/VmHttpUtil.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/VmHttpUtil.kt
new file mode 100644
index 0000000..7ca643b
--- /dev/null
+++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/VmHttpUtil.kt
@@ -0,0 +1,82 @@
+package com.keyborad.theme.trendyborad.environment
+
+import android.content.Context
+import android.net.ConnectivityManager
+import okhttp3.Callback
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import java.io.IOException
+import java.security.SecureRandom
+import java.security.cert.X509Certificate
+import java.util.concurrent.TimeUnit
+import javax.net.ssl.SSLContext
+import javax.net.ssl.TrustManager
+import javax.net.ssl.X509TrustManager
+
+class VmHttpUtil {
+
+ fun isNetworkAvailable(context: Context): Boolean {
+ val connectivityManager =
+ context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ val activeNetworkInfo = connectivityManager?.activeNetworkInfo
+ return activeNetworkInfo != null && activeNetworkInfo.isConnected
+ }
+
+ private var mOkHttpClient: OkHttpClient? = null
+
+ companion object {
+ val mInstance: VmHttpUtil by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
+ VmHttpUtil()
+ }
+ }
+
+ private val CONNECT_TIMEOUT: Long = 60 //超时时间,秒
+
+ private val READ_TIMEOUT: Long = 60 //读取时间,秒
+
+ private val WRITE_TIMEOUT: Long = 60 //写入时间,秒
+
+ init {
+ // ---- 不安全:信任所有证书(仅测试用) ----
+ val trustAllCerts = arrayOf(
+ object : X509TrustManager {
+ override fun checkClientTrusted(chain: Array, authType: String) {}
+ override fun checkServerTrusted(chain: Array, authType: String) {}
+ override fun getAcceptedIssuers(): Array = arrayOf()
+ }
+ )
+
+ val sslContext = SSLContext.getInstance("TLS")
+ sslContext.init(null, trustAllCerts, SecureRandom())
+ val sslSocketFactory = sslContext.socketFactory
+
+ val builder: OkHttpClient.Builder = OkHttpClient.Builder()
+ .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
+ .writeTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
+ .readTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
+ .sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
+ mOkHttpClient = builder.build()
+ }
+
+ private val mediaType = "application/json; charset=utf-8".toMediaType()
+
+ fun getIPInfo(url: String, callback: Callback) {
+ val request: Request = Request.Builder()
+ .url(url)
+ .get()
+ .build()
+ doAsync(request, callback)
+ }
+
+ /**
+ * 异步请求
+ */
+ @Throws(IOException::class)
+ private fun doAsync(request: Request, callback: Callback) {
+ //创建请求会话
+ val call = mOkHttpClient!!.newCall(request)
+ //同步执行会话请求
+ call.enqueue(callback)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdActivityManager.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdActivityManager.kt
new file mode 100644
index 0000000..99a4996
--- /dev/null
+++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdActivityManager.kt
@@ -0,0 +1,177 @@
+package com.keyborad.theme.trendyborad.environment.ad
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.app.Application
+import android.content.Context
+import android.os.Bundle
+import android.provider.Settings
+import android.util.Log
+import com.keyborad.theme.trendyborad.environment.ad.async.Async
+import com.unity3d.services.ads.adunit.AdUnitActivity
+import com.vungle.ads.internal.ui.AdActivity
+import com.vungle.ads.internal.ui.view.MRAIDAdWidget
+
+class AdActivityManager {
+ private var mCurrentShowAd: Activity? = null//记录当前正在展示的inst广告Activity
+ private var mApplicationContext: Context? = null
+ private var mAID: String? = null
+ private var mListener: InstAdLeaveAndReturnListener? = null
+ private var isAdClosed = false//当广告点击了close按钮的时候 设置成true
+ private var isAdClicked = false//标记inst是否已是已点击状态
+
+ companion object {
+ val instance: AdActivityManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
+ AdActivityManager()
+ }
+
+ fun isAdActivity(activity: Activity): Boolean {
+ return (
+ activity is com.tradplus.ads.mgr.interstitial.views.InterNativeActivity
+ || activity is com.vungle.ads.internal.ui.VungleActivity
+ || activity is com.vungle.ads.internal.ui.AdActivity
+ || activity is AdUnitActivity
+ )
+ }
+ }
+
+ fun getCurrentShowAd(): Activity? {
+ return mCurrentShowAd
+ }
+
+ fun doDelayClose(delay: Long) {
+ Async.scheduleTaskOnUiThread(delay, Runnable { doClose() })
+ }
+
+ /**
+ *手动关闭当前显示的inst,具体支持哪些平台的inst,在 onActivityResumed 中添加
+ */
+ fun doClose() {
+ mCurrentShowAd?.let { adActivity ->
+ Log.d("ocean-brush", "自动关闭 $adActivity")
+ when (adActivity) {
+ is AdActivity ->{
+ //反射方式关闭
+ try {
+ // 获取 mraidAdWidget 字段
+ val field = AdActivity::class.java.getDeclaredField("mraidAdWidget")
+ field.isAccessible = true
+ val widget = field.get(adActivity) as? MRAIDAdWidget
+ Log.d("ocean-brush", "调用 widget.close(),触发 CloseDelegate -> finish()")
+ widget?.close() ?: run {
+ // 没拿到 widget
+ adActivity.onBackPressed()
+ Log.d("ocean-brush", "liftoff关闭使用onBackPressed()")
+ }
+ } catch (e: Exception) {
+ adActivity.finish()
+ adActivity.moveTaskToBack(true)
+ Log.d("ocean-brush", "liftoff关闭错误,直接finish")
+ }
+ }
+ else -> {
+ //关闭广告act
+ adActivity.finish()
+ // 强制移除任务栈(适用于某些广告 Activity 仍然存活的情况)
+ adActivity.moveTaskToBack(true)
+ }
+ }
+
+ }
+ }
+
+ fun getContext(): Context? {
+ return mApplicationContext
+ }
+
+ fun getAID(): String? {
+ return mAID
+ }
+
+ fun addActivityLifeCallbacks(callbacks: Application.ActivityLifecycleCallbacks?) {
+ (mApplicationContext as Application).registerActivityLifecycleCallbacks(callbacks)
+ }
+
+ fun removeActivityLifeCallbacks(callbacks: Application.ActivityLifecycleCallbacks?) {
+ (mApplicationContext as Application).unregisterActivityLifecycleCallbacks(callbacks)
+ }
+
+ //在Application中调用
+ @SuppressLint("HardwareIds")
+ fun setControl(application: Application) {
+ mApplicationContext = application
+ application.registerActivityLifecycleCallbacks(object :
+ Application.ActivityLifecycleCallbacks {
+ override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
+
+ }
+
+ override fun onActivityStarted(activity: Activity) {
+
+ }
+
+ override fun onActivityResumed(activity: Activity) {
+ //此处添加想要控制的广告平台的inst Activity
+ try {
+ if (isAdActivity(activity)) {
+ mCurrentShowAd = activity
+ if (isAdClicked) {
+ //点击了admob inst跳转到外部,然后从外部返回app的时候触发
+ mListener?.onReturnAdFromOutside()
+ }
+ }
+ } catch (e: Exception) {
+ }
+ }
+
+ override fun onActivityPaused(activity: Activity) {
+ try {
+ if (mCurrentShowAd != null) {
+ if (isAdActivity(activity)) {//onActivityPaused会在很多场景下触发:1.退到桌面 2.去往app外部 3.被dialog挡住 4.广告点击了close按钮销毁的过程中
+ if (!isAdClosed) {//isAdClosed这里就是为了排除"4.广告点击了close按钮销毁的过程中"
+ //需要在不同展示场景中实际测试
+ mListener?.onLeaveAdGoOutside()
+ isAdClicked = true
+ }
+ }
+ }
+ } catch (e: Exception) {
+ }
+ }
+
+ override fun onActivityStopped(activity: Activity) {}
+ override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
+ override fun onActivityDestroyed(activity: Activity) {
+ Log.d("ocean-brush", "onActivityDestroyed $activity $mCurrentShowAd")
+ if (mCurrentShowAd === activity) {
+ mCurrentShowAd = null
+ isAdClosed = false
+ isAdClicked = false
+ mListener = null
+ }
+ }
+ })
+ try {
+ mAID = hashCode().toString() + ""
+ if (mApplicationContext != null) {
+ mAID = Settings.Secure.getString(
+ mApplicationContext?.contentResolver, Settings.Secure.ANDROID_ID
+ )
+ }
+ } catch (e: Exception) {
+ }
+ }
+
+ fun setAdClose(isClosing: Boolean) {
+ isAdClosed = isClosing
+ }
+
+ fun setInstAdListener(listener: InstAdLeaveAndReturnListener) {
+ mListener = listener
+ }
+
+ interface InstAdLeaveAndReturnListener {
+ fun onLeaveAdGoOutside()//1.点击ad跳转到外部 2.app切换到后台 3.被dialog遮挡
+ fun onReturnAdFromOutside()//从外部返回ad页面, 通常是点击ad跳转到外部后返回ad页面
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdInstLoad.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdInstLoad.kt
new file mode 100644
index 0000000..a4243a1
--- /dev/null
+++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdInstLoad.kt
@@ -0,0 +1,81 @@
+package com.keyborad.theme.trendyborad.environment.ad
+
+import android.app.Activity
+import android.util.Log
+import com.tradplus.ads.base.bean.TPAdError
+import com.tradplus.ads.base.bean.TPAdInfo
+import com.tradplus.ads.open.interstitial.InterstitialAdListener
+import com.tradplus.ads.open.interstitial.TPInterstitial
+
+class AdInstLoad {
+ private var mPlace: String
+ private var adLoadListener: LoadListener? = null
+ private var activity: Activity? = null
+
+ constructor(activity: Activity, place: String, listener: LoadListener?) {
+ this.mPlace = place
+ this.adLoadListener = listener
+ this.activity = activity
+ init()
+ }
+
+ constructor(place: String, listener: LoadListener?) {
+ this.mPlace = place
+ this.adLoadListener = listener
+ init()
+ }
+
+ private fun init() {
+ val tpInterstitial = TPInterstitial(activity, mPlace)
+ tpInterstitial.setAdListener(object : InterstitialAdListener {
+ //广告加载完成 首个广告源加载成功时回调 一次加载流程只会回调一次
+ override fun onAdLoaded(tpAdInfo: TPAdInfo?) {
+ if (tpAdInfo != null) {
+ Log.d("ocean", "广告load成功,tpAdInfo有值")
+ InstAdCacheManager.instance.setAdCache(mPlace, tpInterstitial)
+ adLoadListener?.loaded(tpAdInfo)
+ } else {
+ adLoadListener?.loadFailed("tpAdInfo没有值")
+ Log.d("ocean", "tpAdInfo没有值")
+ }
+ }
+
+ // 广告被点击
+ override fun onAdClicked(tpAdInfo: TPAdInfo?) {
+ Log.d("ocean", "tradplus onAdClicked")
+ }
+
+ // 广告成功展示在页面上
+ override fun onAdImpression(tpAdInfo: TPAdInfo?) {
+ Log.d("ocean", "tradplus onAdImpression")
+ }
+
+ // 广告加载失败
+ override fun onAdFailed(error: TPAdError?) {
+ adLoadListener?.loadFailed("code->${error?.errorCode}message->${error?.errorMsg}")
+ Log.d("ocean", "load ad onError->code->${error?.errorCode}message->${error?.errorMsg}")
+ }
+
+ // 广告被关闭
+ override fun onAdClosed(tpAdInfo: TPAdInfo?) {
+ Log.d("ocean", "tradplus onAdClosed")
+ }
+
+ // 视频播放开始(部分广告源支持)
+ override fun onAdVideoStart(tpAdInfo: TPAdInfo?) {
+ Log.d("ocean", "tradplus onAdVideoStart")
+ }
+
+ //视频播放结束(部分广告源支持)
+ override fun onAdVideoEnd(tpAdInfo: TPAdInfo?) {
+ Log.d("ocean", "tradplus onAdVideoEnd")
+ }
+
+ //视频播放失败(部分广告源支持)
+ override fun onAdVideoError(tpAdInfo: TPAdInfo?, error: TPAdError?) {
+ Log.d("ocean", "onAdVideoError code->${error?.errorCode}message->${error?.errorMsg}")
+ }
+ })
+ tpInterstitial.loadAd()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdInstShower.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdInstShower.kt
new file mode 100644
index 0000000..a6c84b4
--- /dev/null
+++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdInstShower.kt
@@ -0,0 +1,79 @@
+package com.keyborad.theme.trendyborad.environment.ad
+
+import android.app.Activity
+import android.util.Log
+import com.keyborad.theme.trendyborad.environment.ad.async.Async
+import com.tradplus.ads.base.bean.TPAdError
+import com.tradplus.ads.base.bean.TPAdInfo
+import com.tradplus.ads.open.interstitial.InterstitialAdListener
+
+class AdInstShower {
+ private var mPlace: String
+ private var showListener: ShowListener? = null
+ private var activity: Activity? = null
+
+ constructor(activity: Activity, place: String, showListener: ShowListener?) {
+ this.mPlace = place
+ this.showListener = showListener
+ this.activity = activity
+ init()
+ }
+
+ constructor(place: String, showListener: ShowListener?) {
+ this.mPlace = place
+ this.showListener = showListener
+ init()
+ }
+
+ private fun init() {
+ val interstitialAd = InstAdCacheManager.instance.getAdCache(mPlace)
+ interstitialAd?.setAdListener(object : InterstitialAdListener {
+ //广告加载完成 首个广告源加载成功时回调 一次加载流程只会回调一次
+ override fun onAdLoaded(tpAdInfo: TPAdInfo?) {}
+
+ // 广告被点击
+ override fun onAdClicked(tpAdInfo: TPAdInfo?) {
+ showListener?.onAdClicked()
+ Log.d("ocean", "AdInstShower 广告点击回调")
+ }
+
+ // 广告成功展示在页面上
+ override fun onAdImpression(tpAdInfo: TPAdInfo?) {
+ showListener?.onAdShown(tpAdInfo)
+ Log.d("ocean", "AdInstShower 广告展示回调")
+ autoClose()
+ }
+
+ // 广告加载失败
+ override fun onAdFailed(error: TPAdError?) {}
+
+ // 广告被关闭
+ override fun onAdClosed(tpAdInfo: TPAdInfo?) {
+ showListener?.onAdClosed()
+ Log.d("ocean", "AdInstShower 广告关闭回调")
+ }
+
+ // 视频播放开始(部分广告源支持)
+ override fun onAdVideoStart(tpAdInfo: TPAdInfo?) {}
+
+ //视频播放结束(部分广告源支持)
+ override fun onAdVideoEnd(tpAdInfo: TPAdInfo?) {}
+
+ //视频播放失败(部分广告源支持)
+ override fun onAdVideoError(tpAdInfo: TPAdInfo?, error: TPAdError?) {
+ Log.d("ocean", "AdInstShower 视频广告播放失败回调->${error}")
+ showListener?.onAdShowFailed(AdShowFailed(error?.errorMsg.toString()))
+ }
+ })
+ interstitialAd?.showAd(activity!!, mPlace)
+ }
+
+ private fun autoClose() {
+ val autoCloseTime = 3000L
+ Log.d("ocean-brush", "show后开始等待自动关闭广告 $autoCloseTime")
+ Async.scheduleTaskOnUiThread(autoCloseTime, Runnable {
+ AdActivityManager.instance.doClose()
+ Log.d("ocean-brush", "show后执行关闭广告 $autoCloseTime")
+ })
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdShowFailed.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdShowFailed.kt
new file mode 100644
index 0000000..7ec35c1
--- /dev/null
+++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdShowFailed.kt
@@ -0,0 +1,5 @@
+package com.keyborad.theme.trendyborad.environment.ad
+
+data class AdShowFailed(
+ val msg: String = "",
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdsInsUtil.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdsInsUtil.kt
new file mode 100644
index 0000000..6b4a5ff
--- /dev/null
+++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/AdsInsUtil.kt
@@ -0,0 +1,28 @@
+package com.keyborad.theme.trendyborad.environment.ad
+
+import android.app.Activity
+
+object AdsInsUtil {
+
+ object Placement {
+ const val TOP_ON_AD_ONE = "F625C64CEF1F167289F1F50E27276A12"
+ const val TOP_ON_AD_TOW = "303439D05F7C6E4E0E0CA0D9FE803212"
+ const val TOP_ON_AD_THREE = "B94497028A74E381D75C0356BBA54512"
+ }
+
+ fun loadAd(
+ act: Activity,
+ adID: String,
+ loadListener: LoadListener?
+ ): AdInstLoad {
+ return AdInstLoad(act, adID, loadListener)
+ }
+
+ fun showAd(
+ act: Activity,
+ adID: String,
+ listener: ShowListener?
+ ): AdInstShower {
+ return AdInstShower(act, adID, listener)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/InstAdCacheManager.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/InstAdCacheManager.kt
new file mode 100644
index 0000000..2a33380
--- /dev/null
+++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/InstAdCacheManager.kt
@@ -0,0 +1,35 @@
+package com.keyborad.theme.trendyborad.environment.ad
+
+import com.tradplus.ads.open.interstitial.TPInterstitial
+
+class InstAdCacheManager {
+ private val mAdCacheDict: MutableMap = mutableMapOf()
+
+ companion object {
+ val instance: InstAdCacheManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
+ InstAdCacheManager()
+ }
+ }
+
+ fun setAdCache(place: String, adCache: TPInterstitial) {
+ mAdCacheDict[place] = adCache
+ }
+
+ fun getAdCache(place: String): TPInterstitial? {
+ return mAdCacheDict[place]
+ }
+
+ fun getLoadedInstCount(): Int {
+ var count = 0
+ try {
+ mAdCacheDict.forEach { (key, value) ->
+ if (value.isReady) {
+ count += 1
+ }
+ }
+ } catch (_: Exception) {
+
+ }
+ return count
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/LoadListener.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/LoadListener.kt
new file mode 100644
index 0000000..07a371b
--- /dev/null
+++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/LoadListener.kt
@@ -0,0 +1,8 @@
+package com.keyborad.theme.trendyborad.environment.ad
+
+import com.tradplus.ads.base.bean.TPAdInfo
+
+interface LoadListener {
+ fun loadFailed(error: String) {}
+ fun loaded(ad: TPAdInfo) {}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/ShowListener.kt b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/ShowListener.kt
new file mode 100644
index 0000000..7eba6d0
--- /dev/null
+++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/ShowListener.kt
@@ -0,0 +1,10 @@
+package com.keyborad.theme.trendyborad.environment.ad
+
+import com.tradplus.ads.base.bean.TPAdInfo
+
+interface ShowListener {
+ fun onAdShown(ad: TPAdInfo?) {}
+ fun onAdShowFailed(error: AdShowFailed?) {}
+ fun onAdClosed() {}
+ fun onAdClicked() {}
+}
diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/Async.java b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/Async.java
new file mode 100644
index 0000000..4db5912
--- /dev/null
+++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/Async.java
@@ -0,0 +1,83 @@
+package com.keyborad.theme.trendyborad.environment.ad.async;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
+
+public class Async {
+ private static ThreadPoolExecutorWrapper sThreadPoolExecutorWrapper;
+
+ private static ThreadPoolExecutorWrapper getThreadPoolExecutorWrapper() {
+ if (sThreadPoolExecutorWrapper == null) {
+ synchronized (Async.class) {
+ if (sThreadPoolExecutorWrapper == null) {
+ sThreadPoolExecutorWrapper = new ThreadPoolExecutorWrapper(12, 12, 10);
+// if (BuildConfig.DEBUG)
+// LogUtil.d(LogFilterDef.APP_INIT, LogHelper.getFileLineMethod(1));
+ }
+ }
+ }
+
+ return sThreadPoolExecutorWrapper;
+ }
+
+ public static void run(Runnable task) {
+ getThreadPoolExecutorWrapper().executeTask(task);
+ }
+
+ public static Future submit(Callable task) {
+ return getThreadPoolExecutorWrapper().submitTask(task);
+ }
+
+ public static boolean isMainThread() {
+ return Thread.currentThread() == Looper.getMainLooper().getThread();
+ }
+
+ public static void schedule(long delay, Runnable task) {
+ getThreadPoolExecutorWrapper().scheduleTask(delay, task);
+ }
+
+ public static void scheduleTaskAtFixedRateIgnoringTaskRunningTime(long initialDelay, long period, Runnable task) {
+ getThreadPoolExecutorWrapper().scheduleTaskAtFixedRateIgnoringTaskRunningTime(initialDelay, period, task);
+ }
+
+ public static void scheduleTaskAtFixedRateIncludingTaskRunningTime(long initialDelay, long period, Runnable task) {
+ getThreadPoolExecutorWrapper().scheduleTaskAtFixedRateIncludingTaskRunningTime(initialDelay, period, task);
+ }
+
+ public static boolean removeScheduledTask(Runnable task) {
+ return getThreadPoolExecutorWrapper().removeScheduledTask(task);
+ }
+
+ public static void scheduleTaskOnUiThread(long delay, Runnable task) {
+ getThreadPoolExecutorWrapper().scheduleTaskOnUiThread(delay, task);
+ }
+
+ public static void removeScheduledTaskOnUiThread(Runnable task) {
+ getThreadPoolExecutorWrapper().removeScheduledTaskOnUiThread(task);
+ }
+
+ public static void runOnUiThread(Runnable task) {
+ getThreadPoolExecutorWrapper().runTaskOnUiThread(task);
+ }
+
+ public static Handler getMainHandler() {
+ return getThreadPoolExecutorWrapper().getMainHandler();
+ }
+
+ /*
+ single background thread to execute Job
+ */
+ public static void scheduleInQueue(Runnable task) {
+ getThreadPoolExecutorWrapper().scheduleOnQueue(task);
+ }
+
+ public static void shutdown() {
+ if (sThreadPoolExecutorWrapper != null) {
+ sThreadPoolExecutorWrapper.shutdown();
+ sThreadPoolExecutorWrapper = null;
+ }
+ }
+}
diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/HandlerPoster.java b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/HandlerPoster.java
new file mode 100644
index 0000000..129c72c
--- /dev/null
+++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/HandlerPoster.java
@@ -0,0 +1,140 @@
+package com.keyborad.theme.trendyborad.environment.ad.async;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+public class HandlerPoster extends Handler {
+
+ private final int ASYNC = 1;
+
+ private final int SYNC = 2;
+
+ private final Queue asyncPool;
+
+ private final Queue syncPool;
+
+ private final int maxMillisInsideHandleMessage;
+
+ private boolean asyncActive;//执行状态。避免重复发送消息导致消息队列过多
+
+ private boolean syncActive;//执行状态。避免重复发送消息导致消息队列过多
+
+ HandlerPoster(Looper looper, int maxMillisInsideHandleMessage) {
+ super(looper);
+ this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
+ asyncPool = new LinkedList();
+ syncPool = new LinkedList();
+ }
+
+ void dispose() {
+ this.removeCallbacksAndMessages(null);
+ this.asyncPool.clear();
+ this.syncPool.clear();
+ }
+
+ void async(Runnable runnable) throws Exception {
+ synchronized (asyncPool) {
+ asyncPool.offer(runnable);
+ //判断当前是否有异步任务正在执行
+ if (!asyncActive) {
+ asyncActive = true;
+ if (!sendMessage(obtainMessage(ASYNC))) {
+ throw new Exception("Could not send handler message");
+ }
+ }
+ }
+ }
+
+ void sync(SyncPost post) throws Exception {
+ synchronized (syncPool) {
+ syncPool.offer(post);
+ //判断当前是否有同步任务正在执行
+ if (!syncActive) {
+ syncActive = true;
+ if (!sendMessage(obtainMessage(SYNC))) {
+ throw new Exception("Could not send handler message");
+ }
+ }
+ }
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == ASYNC) {
+ boolean rescheduled = false;
+ try {
+ //当执行完一个任务后就判断一次是否超过时间限制,如果超过,那么不管队列中的任务是否执行完成都退出,同时发起一个新的消息到Handler循环队列
+ //在while部分,使用poll从队列取出一个任务,判断是否为空,如果为空进入队列同步块;然后再取一次,再次判断。
+ //如果恰巧在进入同步队列之前有新的任务来了,那么第二次取到的当然就不是 NULL也就会继续执行下去。
+ //反之,如果还是为空;那么重置当前队列的状态为false,同时跳出循环。
+ long started = SystemClock.uptimeMillis();
+ while (true) {
+ Runnable runnable = null;
+ synchronized (asyncPool){
+ runnable = asyncPool.size()==0?null:asyncPool.poll();//2018.12.24 add by xw 如果为空就去null
+ }
+ if (runnable == null) {
+ synchronized (asyncPool) {
+ // Check again, this time in synchronized
+ runnable = asyncPool.poll();
+ if (runnable == null) {
+ asyncActive = false;
+ return;
+ }
+ }
+ }
+ runnable.run();
+ long timeInMethod = SystemClock.uptimeMillis() - started;
+ if (timeInMethod >= maxMillisInsideHandleMessage) {
+ if (!sendMessage(obtainMessage(ASYNC))) {
+ throw new Exception("Could not send handler message");
+ }
+ rescheduled = true;
+ return;
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ asyncActive = rescheduled;
+ }
+ } else if (msg.what == SYNC) {
+ boolean rescheduled = false;
+ try {
+ long started = SystemClock.uptimeMillis();
+ while (true) {
+ SyncPost post = syncPool.poll();
+ if (post == null) {
+ synchronized (syncPool) {
+ // Check again, this time in synchronized
+ post = syncPool.poll();
+ if (post == null) {
+ syncActive = false;
+ return;
+ }
+ }
+ }
+ post.run();
+ long timeInMethod = SystemClock.uptimeMillis() - started;
+ if (timeInMethod >= maxMillisInsideHandleMessage) {
+ if (!sendMessage(obtainMessage(SYNC))) {
+ throw new Exception("Could not send handler message");
+ }
+ rescheduled = true;
+ return;
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ syncActive = rescheduled;
+ }
+ } else
+ super.handleMessage(msg);
+ }
+}
diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/MainThreadSwitcher.java b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/MainThreadSwitcher.java
new file mode 100644
index 0000000..34e0c37
--- /dev/null
+++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/MainThreadSwitcher.java
@@ -0,0 +1,73 @@
+package com.keyborad.theme.trendyborad.environment.ad.async;
+
+import android.os.Looper;
+
+/**
+ * 网上找到的关于子线程切换到主线程的代码,实现子线程任意时刻切换到主线程,并可选择地阻塞子线程。
+ * 此方案避免handler满天飞的情况。
+ * 参考资料:http://c.jinhusns.com/cms/c-884
+ * Created by DonWZ on 2016-10-18
+ */
+public class MainThreadSwitcher {
+ private static HandlerPoster mainPoster = null;
+
+ private static HandlerPoster getMainPoster() {
+ if (mainPoster == null) {
+ synchronized (MainThreadSwitcher.class) {
+ if (mainPoster == null) {
+ mainPoster = new HandlerPoster(Looper.getMainLooper(), 500);//限制主线程单次运行时间
+ }
+ }
+ }
+ return mainPoster;
+ }
+
+ /**
+ * Asynchronously.
+ * The child thread asynchronous run relative to the main thread,
+ * not blocking the child thread
+ * @param runnable
+ * Runnable Interface
+ */
+ public static void runOnMainThreadAsync(Runnable runnable) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ runnable.run();
+ return;
+ }
+ try {
+ getMainPoster().async(runnable);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ *
+ * Synchronously.
+ * The child thread relative thread synchronization operation,
+ * blocking the child thread,
+ * thread for the main thread to complete
+ * @param runnable
+ * Runnable Interface
+ */
+ public static void runOnMainThreadSync(Runnable runnable) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ runnable.run();
+ return;
+ }
+ SyncPost poster = new SyncPost(runnable);
+ try {
+ getMainPoster().sync(poster);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ poster.waitRun();
+ }
+
+ public static void dispose() {
+ if (mainPoster != null) {
+ mainPoster.dispose();
+ mainPoster = null;
+ }
+ }
+}
diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/SyncPost.java b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/SyncPost.java
new file mode 100644
index 0000000..215f4ac
--- /dev/null
+++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/SyncPost.java
@@ -0,0 +1,44 @@
+package com.keyborad.theme.trendyborad.environment.ad.async;
+
+public class SyncPost {
+
+ boolean end = false;
+
+ Runnable runnable;
+
+ SyncPost(Runnable runnable) {
+ this.runnable = runnable;
+ }
+
+ public void run() {
+ //进入同步块,然后调用Runnable接口的run方法。同时在执行完成后将end重置为true;
+ //然后调用this.notifyAll();通知等待的部分可以继续了,当然有这样的情况;假如在进入该同步块的时候子线程还未执行到this.wait();部分呢?
+ //所以我们为此准备了end和try。
+ synchronized (this) {
+ runnable.run();
+ end = true;
+ try {
+ this.notifyAll();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public void waitRun() {
+ //首先判断状态,如果状态已经变了,那么证明子线程执行到此处时,主线程已经执行了void_run()。
+ //所以也就不用进入同步块进行等待了。反之进入等待直到主线程调用this.notifyAll();
+ if (!end) {
+ synchronized (this) {
+ if (!end) {
+ try {
+ this.wait();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/TaskQueue.java b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/TaskQueue.java
new file mode 100644
index 0000000..9d84f65
--- /dev/null
+++ b/app/src/main/java/com/keyborad/theme/trendyborad/environment/ad/async/TaskQueue.java
@@ -0,0 +1,46 @@
+package com.keyborad.theme.trendyborad.environment.ad.async;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class TaskQueue extends Thread{
+ private BlockingQueue