diff --git a/app/src/main/java/com/gh/common/databind/BindingAdapters.java b/app/src/main/java/com/gh/common/databind/BindingAdapters.java index 35ca641f0b..307396475a 100644 --- a/app/src/main/java/com/gh/common/databind/BindingAdapters.java +++ b/app/src/main/java/com/gh/common/databind/BindingAdapters.java @@ -15,6 +15,8 @@ import android.widget.TextView; import com.facebook.drawee.view.SimpleDraweeView; import com.gh.base.OnViewClickListener; import com.gh.common.constant.Config; +import com.gh.common.exposure.ExposureEvent; +import com.gh.common.exposure.ExposureUtils; import com.gh.common.util.DataUtils; import com.gh.common.util.DialogUtils; import com.gh.common.util.DisplayUtils; @@ -277,8 +279,8 @@ public class BindingAdapters { } } - @BindingAdapter("downloadButton") - public static void setDownloadButton(DownloadProgressBar progressBar, GameEntity gameEntity) { + @BindingAdapter({"downloadButton", "traceEvent"}) + public static void setDownloadButton(DownloadProgressBar progressBar, GameEntity gameEntity, ExposureEvent traceEvent) { // 判断是否显示按钮 if (gameEntity != null && Config.isShowDownload(gameEntity.getId()) @@ -364,15 +366,15 @@ public class BindingAdapters { case PLUGIN: if (gameEntity.getApk().size() == 1) { if (NetworkUtils.isWifiConnected(v.getContext())) { - download(progressBar, gameEntity); + download(progressBar, gameEntity, traceEvent); } else { DialogUtils.showDownloadDialog(v.getContext(), () -> { - download(progressBar, gameEntity); + download(progressBar, gameEntity, traceEvent); }); } } else { DownloadDialog.getInstance(v.getContext()).showPopupWindow(v, gameEntity, - "(我的光环:我的游戏)", "我的光环-我的游戏:" + gameEntity.getName()); + "(我的光环:我的游戏)", "我的光环-我的游戏:" + gameEntity.getName(), traceEvent); } break; case LAUNCH_OR_OPEN: @@ -381,7 +383,7 @@ public class BindingAdapters { PackageUtils.launchApplicationByPackageName(v.getContext(), gameEntity.getApk().get(0).getPackageName()); } else { DownloadDialog.getInstance(v.getContext()).showPopupWindow(v, gameEntity, - "(我的光环:我的游戏)", "我的光环-我的游戏:" + gameEntity.getName()); + "(我的光环:我的游戏)", "我的光环-我的游戏:" + gameEntity.getName(), traceEvent); } break; case INSTALL_PLUGIN: @@ -393,7 +395,7 @@ public class BindingAdapters { } // 开始下载 - private static void download(DownloadProgressBar progressBar, GameEntity gameEntity) { + private static void download(DownloadProgressBar progressBar, GameEntity gameEntity, ExposureEvent traceEvent) { String str = progressBar.getText(); String method; if (str.contains("更新")) { @@ -408,7 +410,14 @@ public class BindingAdapters { if (TextUtils.isEmpty(msg)) { DataUtils.onGameDownloadEvent(progressBar.getContext(), gameEntity.getName(), apkEntity.getPlatform(), "(我的光环:我的游戏)", "下载开始"); - DownloadManager.createDownload(progressBar.getContext(), apkEntity, gameEntity, method, StringUtils.buildString("(我的光环:我的游戏)"), "我的光环-我的游戏:" + gameEntity.getName()); + ExposureEvent downloadExposureEvent = ExposureUtils.INSTANCE.logADownloadExposureEvent(gameEntity, apkEntity.getPlatform(), traceEvent, ExposureUtils.DownloadType.DOWNLOAD); + + DownloadManager.createDownload(progressBar.getContext(), + apkEntity, + gameEntity, + method, + StringUtils.buildString("(我的光环:我的游戏)"), "我的光环-我的游戏:" + gameEntity.getName(), + downloadExposureEvent); progressBar.setProgress(0); progressBar.setDownloadType("插件化".equals(method) ? diff --git a/app/src/main/java/com/gh/common/exposure/ExposureConverters.kt b/app/src/main/java/com/gh/common/exposure/ExposureConverters.kt new file mode 100644 index 0000000000..ac093bc2fc --- /dev/null +++ b/app/src/main/java/com/gh/common/exposure/ExposureConverters.kt @@ -0,0 +1,61 @@ +package com.gh.common.exposure + +import android.arch.persistence.room.TypeConverter +import com.gh.common.exposure.meta.Meta +import com.google.gson.Gson +import java.util.* +import kotlin.collections.ArrayList + +class ExposureConverters { + + @TypeConverter + fun convertPayload2String(any: ExposureEntity): String { + return Gson().toJson(any) + } + + @TypeConverter + fun convertString2Payload(string: String): ExposureEntity { + return Gson().fromJson(string, ExposureEntity::class.java) + } + + @TypeConverter + fun convertSource2String(sourceList: List): String { + return Gson().toJson(sourceList) + } + + @TypeConverter + fun convertString2Source(sourceList: String): List { + return ArrayList(Arrays.asList(Gson().fromJson(sourceList, Array::class.java))) as List + } + + @TypeConverter + fun convertETrace2String(sourceList: List?): String { + return Gson().toJson(sourceList) + } + + @TypeConverter + fun convertStringToETrace(sourceList: String): List { + return ArrayList(Arrays.asList(Gson().fromJson(sourceList, Array::class.java))) as List + } + + @TypeConverter + fun convertExposeType2String(exposureType: ExposureType): String { + return exposureType.toString() + } + + @TypeConverter + fun convertStringToExposeType(exposureType: String): ExposureType { + return ExposureType.valueOf(exposureType) + } + + @TypeConverter + fun convertMeta2String(any: Meta): String { + return Gson().toJson(any) + } + + @TypeConverter + fun convertString2Meta(string: String): Meta { + return Gson().fromJson(string, Meta::class.java) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/exposure/ExposureDatabase.kt b/app/src/main/java/com/gh/common/exposure/ExposureDatabase.kt new file mode 100644 index 0000000000..744b6a5470 --- /dev/null +++ b/app/src/main/java/com/gh/common/exposure/ExposureDatabase.kt @@ -0,0 +1,23 @@ +package com.gh.common.exposure + +import android.arch.persistence.room.Database +import android.arch.persistence.room.Room +import android.arch.persistence.room.RoomDatabase +import android.arch.persistence.room.TypeConverters +import android.content.Context + +@TypeConverters(ExposureConverters::class) +@Database(entities = [ExposureEvent::class], version = 1, exportSchema = false) +abstract class ExposureDatabase : RoomDatabase() { + companion object { + private const val DATABASE = "exposure_database" + + fun buildDatabase(context: Context): ExposureDatabase { + return Room.databaseBuilder(context, ExposureDatabase::class.java, DATABASE) + .fallbackToDestructiveMigration() + .build() + } + } + + abstract fun logHubEventDao(): ExposureEventDao +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/exposure/ExposureEntity.kt b/app/src/main/java/com/gh/common/exposure/ExposureEntity.kt new file mode 100644 index 0000000000..d79c43c7cb --- /dev/null +++ b/app/src/main/java/com/gh/common/exposure/ExposureEntity.kt @@ -0,0 +1,18 @@ +package com.gh.common.exposure + +import android.os.Parcelable +import android.support.annotation.Keep +import com.google.gson.annotations.SerializedName +import kotlinx.android.parcel.Parcelize + +@Keep +@Parcelize +data class ExposureEntity( + @SerializedName("game_id") + val gameId: String? = "", + val gameName: String? = "", + val sequence: Int? = 0, + val platform: String? = "", + val downloadType: String? = "", + val downloadCompleteType: String? = "" +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/exposure/ExposureEvent.kt b/app/src/main/java/com/gh/common/exposure/ExposureEvent.kt new file mode 100644 index 0000000000..983c14c7e9 --- /dev/null +++ b/app/src/main/java/com/gh/common/exposure/ExposureEvent.kt @@ -0,0 +1,40 @@ +package com.gh.common.exposure + +import android.arch.persistence.room.Entity +import android.arch.persistence.room.PrimaryKey +import android.os.Parcelable +import android.support.annotation.Keep +import com.gh.common.exposure.meta.Meta +import com.gh.common.exposure.meta.MetaUtil +import com.gh.common.exposure.time.TimeUtil +import com.gh.gamecenter.entity.GameEntity +import kotlinx.android.parcel.Parcelize +import java.util.* + +@Keep +@Parcelize +@Entity(tableName = "exposureEvent") +data class ExposureEvent( + val payload: ExposureEntity, + val source: List, + var eTrace: List? = arrayListOf(), + val event: ExposureType, + val meta: Meta = MetaUtil.getMeta(), + val time: Int = TimeUtil.currentTime(), + @PrimaryKey + val id: String = UUID.randomUUID().toString()) : Parcelable { + companion object { + fun createEvent(gameEntity: GameEntity?, source: List, eTrace: List?, event: ExposureType): ExposureEvent { + return ExposureEvent( + ExposureEntity(gameId = gameEntity?.id, + gameName = gameEntity?.name, + sequence = gameEntity?.sequence, + platform = gameEntity?.platform, + downloadType = gameEntity?.downloadType, + downloadCompleteType = gameEntity?.downloadCompleteType), + source = source, + eTrace = eTrace, + event = event) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/exposure/ExposureEventDao.kt b/app/src/main/java/com/gh/common/exposure/ExposureEventDao.kt new file mode 100644 index 0000000000..20d2ebf2dc --- /dev/null +++ b/app/src/main/java/com/gh/common/exposure/ExposureEventDao.kt @@ -0,0 +1,19 @@ +package com.gh.common.exposure + +import android.arch.persistence.room.* + +@Dao +interface ExposureEventDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertMany(eventList: List) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(event: ExposureEvent) + + @Query("SELECT * FROM exposureEvent") + fun getAll(): List + + @Delete + fun deleteMany(eventList: List) +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/exposure/ExposureListener.kt b/app/src/main/java/com/gh/common/exposure/ExposureListener.kt new file mode 100644 index 0000000000..9fb5bc29aa --- /dev/null +++ b/app/src/main/java/com/gh/common/exposure/ExposureListener.kt @@ -0,0 +1,70 @@ +package com.gh.common.exposure + +import android.support.v4.app.Fragment +import android.support.v4.app.FragmentManager +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView +import rx.functions.Action1 + +/** + * Exposure Event Listener for RecyclerView + * + * TODO 1. Pull down refresh change in first page without scroll down action + * TODO 2. Item change not triggered by user scroll action (vm data change etc.) + */ +class ExposureListener(var fragment: Fragment, var exposable: IExposable) : RecyclerView.OnScrollListener() { + + var throttleBus: ExposureThrottleBus? = null + var layoutManager: LinearLayoutManager? = null + var visibleState: ExposureThrottleBus.VisibleState? = null + + init { + fragment.fragmentManager?.registerFragmentLifecycleCallbacks( + object : FragmentManager.FragmentLifecycleCallbacks() { + override fun onFragmentResumed(fm: FragmentManager?, f: Fragment?) { + throttleBus = ExposureThrottleBus(Action1 { commitExposure(it) }, Action1(Throwable::printStackTrace)) + } + + override fun onFragmentPaused(fm: FragmentManager?, f: Fragment?) { + visibleState?.let { commitExposure(it) } + throttleBus?.unsubscribe() + } + }, false) + } + + /** + * Monitor items in display when scrolling, record those newly displayed as well as + * those newly disappeared. And finally trigger commitExposure(). + */ + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + + if (layoutManager == null) layoutManager = recyclerView.layoutManager as LinearLayoutManager + + layoutManager?.run { + visibleState = ExposureThrottleBus.VisibleState(findFirstCompletelyVisibleItemPosition(), findLastCompletelyVisibleItemPosition()) + throttleBus?.postVisibleState(visibleState!!) + } + } + + /** + * Check disappearMap items together with according data in displayMap, + * log any items displayed long enough to be called a EXPOSURE + */ + private fun commitExposure(visibleState: ExposureThrottleBus.VisibleState) { + + val eventList = arrayListOf() + + for (pos in visibleState.firstCompletelyVisible..visibleState.lastCompletelyVisible) { + try { + exposable.getEventByPosition(pos)?.let { eventList.add(it) } + exposable.getEventListByPosition(pos)?.let { eventList.addAll(it) } + } catch (e: Exception) { + e.printStackTrace() + } + } + + ExposureManager.log(eventList) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/exposure/ExposureManager.kt b/app/src/main/java/com/gh/common/exposure/ExposureManager.kt new file mode 100644 index 0000000000..5139dd3b23 --- /dev/null +++ b/app/src/main/java/com/gh/common/exposure/ExposureManager.kt @@ -0,0 +1,172 @@ +package com.gh.common.exposure + +import android.app.Application +import android.util.Log +import com.aliyun.sls.android.sdk.LogException +import com.aliyun.sls.android.sdk.model.LogGroup +import com.gh.common.exposure.aliyun.LGLOG +import com.gh.common.exposure.aliyun.LGLOGClient +import com.gh.common.exposure.meta.MetaUtil +import com.gh.common.exposure.time.TimeUtil +import com.google.gson.Gson +import com.halo.assistant.HaloApp +import java.util.concurrent.Executors +import kotlin.concurrent.fixedRateTimer + +/** + * ExposureManager tool to commit logs to aliyun loghub + * TODO handle logs that failed to be committed multiple times + */ +object ExposureManager { + + private var TAG: String = ExposureManager::class.java.simpleName + + private const val ACCESS_KEY_ID = "LTAIV3i0sNc4TPK1" + private const val ACCESS_KEY_SECRET = "8dKtTPeE5WYA6ZCeuIBcIVp7eB0ir4" + private const val ENDPOINT = "cn-qingdao.log.aliyuncs.com" + private const val PROJECT = "ghzs" + private const val LOG_STORE = "test" + private const val STORE_SIZE = 100 + private const val STORE_FORCE_UPLOAD_PERIOD = 300 * 1000L + + private lateinit var client: LGLOGClient + private lateinit var db: ExposureEventDao + private val store = arrayListOf() + private val storeOpThread = Executors.newSingleThreadExecutor() + private val gson = Gson() + + /** + * Must be called early to init object then real use (for example in Application) + */ + fun init(application: Application) { + + client = LGLOGClient(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET, PROJECT) + db = ExposureDatabase.buildDatabase(application).logHubEventDao() + + storeOpThread.execute({ + val eventList = db.getAll() + store.addAll(eventList) + android.util.Log.i(TAG, "store-size:" + eventList.size) + }) + + fixedRateTimer(name = "ExposureManager-Store-Checker", initialDelay = 500, period = STORE_FORCE_UPLOAD_PERIOD) { + checkAndUploadFromStore(true) + } + + MetaUtil.init(application) + TimeUtil.init() + } + + /** + * Log an Event + */ + fun log(event: ExposureEvent, uploadImmediately: Boolean = false) = when (uploadImmediately) { + false -> store(event) + true -> upload(event) + } + + /** + * Log Many Events + */ + fun log(eventList: List, uploadImmediately: Boolean = false) = when (uploadImmediately) { + false -> store(eventList) + true -> upload(eventList) + } + + /** + * Store an Event to store, upload when store size exceeds STORE_SIZE + */ + private fun store(event: ExposureEvent) { + Log.d(TAG, "log -> name:${event.payload.gameName}, type:${event.event}") + store.add(event) + storeOpThread.execute({ + db.insert(event) + }) + checkAndUploadFromStore() + } + + /** + * Store Many Events to store, upload when store size exceeds STORE_SIZE + */ + private fun store(eventList: List) { + store.addAll(eventList) + storeOpThread.execute({ + db.insertMany(eventList) + }) + checkAndUploadFromStore() + } + + /** + * Upload an Event + */ + private fun upload(event: ExposureEvent) { + HaloApp.getInstance().mainExecutor.execute({ + client.PostLog(buildLogGroup(event), LOG_STORE) + }) + } + + /** + * Upload Many Events + */ + private fun upload(eventList: List) { + HaloApp.getInstance().mainExecutor.execute({ + client.PostLog(buildLogGroup(eventList), LOG_STORE) + }) + } + + /** + * Upload Events From Store, and removed them + */ + private fun checkAndUploadFromStore(isForceUpload: Boolean = false) { + if (store.size < STORE_SIZE && !isForceUpload || store.size == 0) return + storeOpThread.execute({ + if (store.size < STORE_SIZE && !isForceUpload || store.size == 0) return@execute + val uploaded = store.toList() + try { + Log.d(TAG, "uploaded size" + uploaded.size) + client.PostLog(buildLogGroup(uploaded), LOG_STORE) + } catch (exception: LogException) { + Log.e(TAG, "LOGHUB", exception) + // Return to insure no logs lost because of online commit failure + return@execute + } + store.removeAll(uploaded) + db.deleteMany(uploaded) + }) + } + + private fun buildLog(event: ExposureEvent): LGLOG { + val log = LGLOG() + + log.PutContent("id", event.id) + log.PutContent("payload", gson.toJson(event.payload)) + log.PutContent("event", event.event.toString()) + log.PutContent("source", gson.toJson(event.source)) + log.PutContent("meta", gson.toJson(event.meta)) + log.PutContent("e-traces", gson.toJson(event.eTrace)) + log.PutTime(event.time) + + return log + } + + private fun buildLogGroup(event: ExposureEvent): LogGroup { + + val logGroup = LogGroup("sls android", "no ip") + + logGroup.PutLog(buildLog(event)) + + return logGroup + } + + private fun buildLogGroup(eventList: List): LogGroup { + + val logGroup = LogGroup("sls android", "no ip") + + eventList.forEach { event -> + logGroup.PutLog(buildLog(event)) + } + + return logGroup + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/exposure/ExposureSource.kt b/app/src/main/java/com/gh/common/exposure/ExposureSource.kt new file mode 100644 index 0000000000..4ed3aaac33 --- /dev/null +++ b/app/src/main/java/com/gh/common/exposure/ExposureSource.kt @@ -0,0 +1,9 @@ +package com.gh.common.exposure + +import android.os.Parcelable +import android.support.annotation.Keep +import kotlinx.android.parcel.Parcelize + +@Keep +@Parcelize +data class ExposureSource(var k: String, var v: String): Parcelable \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/exposure/ExposureThrottleBus.kt b/app/src/main/java/com/gh/common/exposure/ExposureThrottleBus.kt new file mode 100644 index 0000000000..212e895040 --- /dev/null +++ b/app/src/main/java/com/gh/common/exposure/ExposureThrottleBus.kt @@ -0,0 +1,60 @@ +package com.gh.common.exposure + +import rx.Subscription +import rx.functions.Action1 +import rx.schedulers.Schedulers +import rx.subjects.PublishSubject +import rx.subjects.Subject +import java.util.concurrent.TimeUnit + +class ExposureThrottleBus(var onSuccess: Action1, var onError: Action1) { + + companion object { + private const val THRESHOLD_TIME = 300L + } + + private val mPublishSubject: Subject + private val mSubscription: Subscription + + init { + mPublishSubject = PublishSubject.create() + + /** + * Since onScroll() callback will be triggered multiple times for every swipe, we use + * distinctUntilChanged() to prevent committing the same visibleState event and + * throttleWithTimeout() to pass a visibleState event with a delay and drop current event if another event arrives before the timeout. + */ + mSubscription = mPublishSubject + .distinctUntilChanged() + .throttleWithTimeout(THRESHOLD_TIME, TimeUnit.MILLISECONDS) + .subscribeOn(Schedulers.io()) + .subscribe(onSuccess, onError) + } + + fun unsubscribe() { + mSubscription.unsubscribe() + } + + fun postVisibleState(visibleState: VisibleState) { + mPublishSubject.onNext(visibleState) + } + + class VisibleState(val firstCompletelyVisible: Int, val lastCompletelyVisible: Int) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || javaClass != other.javaClass) return false + + val that = other as VisibleState + + if (firstCompletelyVisible != that.firstCompletelyVisible) return false + + return lastCompletelyVisible == that.lastCompletelyVisible + } + + override fun hashCode(): Int { + var result = firstCompletelyVisible + result = 31 * result + lastCompletelyVisible + return result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/exposure/ExposureTraceUtils.kt b/app/src/main/java/com/gh/common/exposure/ExposureTraceUtils.kt new file mode 100644 index 0000000000..dc63e52836 --- /dev/null +++ b/app/src/main/java/com/gh/common/exposure/ExposureTraceUtils.kt @@ -0,0 +1,25 @@ +package com.gh.common.exposure + +object ExposureTraceUtils { + + fun appendTrace(event: ExposureEvent?): List { + val traceList = arrayListOf() + + event?.let { + if (event.eTrace == null) { + traceList.add(event) + } else { + traceList.addAll(event.eTrace!!) + traceList.add(flattenTrace(event)) + } + } + + return traceList + } + + private fun flattenTrace(event: ExposureEvent): ExposureEvent { + event.eTrace = null + return event + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/exposure/ExposureType.kt b/app/src/main/java/com/gh/common/exposure/ExposureType.kt new file mode 100644 index 0000000000..65c98690b8 --- /dev/null +++ b/app/src/main/java/com/gh/common/exposure/ExposureType.kt @@ -0,0 +1,11 @@ +package com.gh.common.exposure + +enum class ExposureType { + EXPOSURE, + + CLICK, + + DOWNLOAD, + + DOWNLOAD_COMPLETE +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/exposure/ExposureUtils.kt b/app/src/main/java/com/gh/common/exposure/ExposureUtils.kt new file mode 100644 index 0000000000..ca9485ad58 --- /dev/null +++ b/app/src/main/java/com/gh/common/exposure/ExposureUtils.kt @@ -0,0 +1,42 @@ +package com.gh.common.exposure + +import com.gh.gamecenter.entity.GameEntity +import com.google.gson.Gson +import java.util.* + +object ExposureUtils { + + fun logADownloadExposureEvent(entity: GameEntity, platform: String?, traceEvent: ExposureEvent?, downloadType: DownloadType): ExposureEvent { + val gameEntity = entity.clone() + gameEntity.platform = platform + gameEntity.downloadType = downloadType.toString() + val exposureEvent = ExposureEvent.createEvent(gameEntity = gameEntity, + source = traceEvent?.source ?: ArrayList(), + eTrace = ExposureTraceUtils.appendTrace(traceEvent), + event = ExposureType.DOWNLOAD) + ExposureManager.log(exposureEvent, false) + return exposureEvent + } + + fun logADownloadCompleteExposureEvent(entity: GameEntity, platform: String?, trace: String?, downloadType: DownloadType) { + val gameEntity = entity.clone() + gameEntity.platform = platform + gameEntity.downloadCompleteType = downloadType.toString() + val traceEvent = Gson().fromJson(trace, ExposureEvent::class.java) + val exposureEvent = ExposureEvent.createEvent(gameEntity = gameEntity, + source = traceEvent?.source ?: ArrayList(), + eTrace = ExposureTraceUtils.appendTrace(traceEvent), + event = ExposureType.DOWNLOAD_COMPLETE) + ExposureManager.log(exposureEvent, false) + } + + enum class DownloadType { + DOWNLOAD, + + UPDATE, + + PLUGIN_UPDATE, + + PLUGIN_DOWNLOAD + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/exposure/IExposable.kt b/app/src/main/java/com/gh/common/exposure/IExposable.kt new file mode 100644 index 0000000000..c83825c793 --- /dev/null +++ b/app/src/main/java/com/gh/common/exposure/IExposable.kt @@ -0,0 +1,7 @@ +package com.gh.common.exposure + +interface IExposable { + fun getEventByPosition(pos: Int): ExposureEvent? + + fun getEventListByPosition(pos: Int): List? +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/exposure/aliyun/LGLOG.java b/app/src/main/java/com/gh/common/exposure/aliyun/LGLOG.java new file mode 100644 index 0000000000..d1578132e1 --- /dev/null +++ b/app/src/main/java/com/gh/common/exposure/aliyun/LGLOG.java @@ -0,0 +1,42 @@ +package com.gh.common.exposure.aliyun; + +import com.aliyun.sls.android.sdk.model.Log; +import com.gh.common.exposure.time.TimeUtil; + +import java.util.HashMap; +import java.util.Map; + +/** + * Extend to change __time__ field in mContent to use the correct time from TimeUtil + */ +public class LGLOG extends Log { + + private Map mContent = new HashMap(); + + public LGLOG() { + mContent.put("__time__", TimeUtil.INSTANCE.currentTime()); + } + + @Override + public void PutTime(int time) { + mContent.put("__time__", time); + } + + @Override + public void PutContent(String key, String value) { + if (key == null || key.isEmpty()) { + return; + } + if (value == null) { + mContent.put(key, ""); + } else { + mContent.put(key, value); + } + } + + @Override + public Map GetContent() { + return mContent; + } + +} diff --git a/app/src/main/java/com/gh/common/exposure/aliyun/LGLOGClient.java b/app/src/main/java/com/gh/common/exposure/aliyun/LGLOGClient.java new file mode 100644 index 0000000000..36209d57c1 --- /dev/null +++ b/app/src/main/java/com/gh/common/exposure/aliyun/LGLOGClient.java @@ -0,0 +1,67 @@ +package com.gh.common.exposure.aliyun; + +import com.aliyun.sls.android.sdk.LOGClient; +import com.aliyun.sls.android.sdk.LogException; +import com.gh.common.exposure.time.TimeUtil; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +/** + * Extend to override GetHttpHeadersFrom, so we can change "Date" header attribute using + * correct time from TimeUtil. + * And accordingly, the value of "Authorization" attribute should also be re-calculate + * since the sign became different as before. + */ +public class LGLOGClient extends LOGClient { + + private String mAccessKeyID; + private String mAccessKeySecret; + private String mAccessToken; + + public LGLOGClient(String endPoint, String accessKeyID, String accessKeySecret, String projectName) { + super(endPoint, accessKeyID, accessKeySecret, projectName); + this.mAccessKeyID = accessKeyID; + this.mAccessKeySecret = accessKeySecret; + this.mAccessToken = ""; + } + + @Override + public Map GetHttpHeadersFrom(String logStoreName, byte[] body, byte[] bodyZipped) throws LogException { + + Map headers = super.GetHttpHeadersFrom(logStoreName, body, bodyZipped); + + SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); + sdf.setTimeZone(TimeZone.getTimeZone("GMT")); // 设置时区为GMT + String str = sdf.format(new Date(TimeUtil.INSTANCE.currentTimeMillis())); + headers.put("Date", str); + + StringBuilder signStringBuf = new StringBuilder("POST" + "\n"). + append(headers.get("Content-MD5") + "\n"). + append(headers.get("Content-Type") + "\n"). + append(headers.get("Date") + "\n"); + String token = mAccessToken; + if (token != null && token != "") { + headers.put("x-acs-security-token", token); + signStringBuf.append("x-acs-security-token:" + headers.get("x-acs-security-token") + "\n"); + } + signStringBuf.append("x-log-apiversion:0.6.0\n"). + append("x-log-bodyrawsize:" + headers.get("x-log-bodyrawsize") + "\n"). + append("x-log-compresstype:deflate\n"). + append("x-log-signaturemethod:hmac-sha1\n"). + append("/logstores/" + logStoreName + "/shards/lb"); + String signString = signStringBuf.toString(); + try { + String sign = hmac_sha1(signString, mAccessKeySecret); + headers.put("Authorization", "LOG " + mAccessKeyID + ":" + sign); + } catch (Exception e) { + throw new LogException("LogClientError", "fail to get encode signature", e, ""); + } + + return headers; + } + + +} diff --git a/app/src/main/java/com/gh/common/exposure/meta/Meta.kt b/app/src/main/java/com/gh/common/exposure/meta/Meta.kt new file mode 100644 index 0000000000..68168b7923 --- /dev/null +++ b/app/src/main/java/com/gh/common/exposure/meta/Meta.kt @@ -0,0 +1,19 @@ +package com.gh.common.exposure.meta + +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class Meta( + val mac: String, + val imei: String, + val model: String, + val manufacturer: String, + val android_id: String, + val android_sdk: Int, + val android_version: String, + val network: String, + val ip: String, + val os: String, + val appVersion: String +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/exposure/meta/MetaUtil.kt b/app/src/main/java/com/gh/common/exposure/meta/MetaUtil.kt new file mode 100644 index 0000000000..f6457574f1 --- /dev/null +++ b/app/src/main/java/com/gh/common/exposure/meta/MetaUtil.kt @@ -0,0 +1,145 @@ +package com.gh.common.exposure.meta + +import android.Manifest +import android.app.Application +import android.content.Context +import android.content.pm.PackageManager +import android.net.ConnectivityManager +import android.net.wifi.WifiManager +import android.os.Build +import android.provider.Settings +import android.telephony.TelephonyManager +import android.text.TextUtils +import com.gh.gamecenter.BuildConfig +import java.io.File + +object MetaUtil { + + private lateinit var application: Application + + fun init(application: Application) { + MetaUtil.application = application + } + + fun getMeta(): Meta { + return Meta(getMac(), getIMEI(), getModel(), getManufacturer(), getAndroidId(), getAndroidSDK(), + getAndroidVersion(), getNetwork(), getIP(), getOS(), BuildConfig.VERSION_NAME) + } + + /** + * Get MAC address + * TODO check > 6.0 results + */ + fun getMac(): String { + + var mac: String + + //Plan A + try { + mac = File("/sys/class/net/wlan0/address").inputStream().bufferedReader().use { it.readText() } + if (!TextUtils.isEmpty(mac)) return mac.trim() + } catch (e: Exception) { +// e.printStackTrace() + } + + // Plan B + try { + mac = File("/sys/class/net/eth0/address").inputStream().bufferedReader().use { it.readText() } + if (!TextUtils.isEmpty(mac)) return mac.trim() + } catch (e: Exception) { +// e.printStackTrace() + } + + // Plan C + val wifiManager = application.getSystemService(Context.WIFI_SERVICE) as WifiManager + mac = wifiManager.connectionInfo.macAddress + return mac.trim() + + } + + /** + * Get IMEI + */ + fun getIMEI(): String { + + if ( application.checkCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED ) + return "" + + val telephonyManager = application.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager + + if (Build.VERSION.SDK_INT >= 26) { + return "!" + telephonyManager.imei + } + + return telephonyManager.getDeviceId() + + } + + fun getModel(): String { + return Build.MODEL + } + + fun getManufacturer(): String { + return Build.MANUFACTURER + } + + fun getAndroidId(): String { + var android_id: String = "" + try { + android_id = Settings.Secure.getString(application.contentResolver, Settings.Secure.ANDROID_ID) + } catch (e: Exception) { + e.printStackTrace() + } + return android_id + } + + fun getAndroidSDK(): Int { + return Build.VERSION.SDK_INT + } + + fun getAndroidVersion(): String { + return Build.VERSION.RELEASE + } + + fun getNetwork(): String { + + if ( application.checkCallingOrSelfPermission(Manifest.permission.ACCESS_NETWORK_STATE) != PackageManager.PERMISSION_GRANTED ) + return "unknown" + + val activeNetwork = ( application.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager ).activeNetworkInfo ?: return "unknown" + + return when(activeNetwork.type) { + ConnectivityManager.TYPE_WIFI -> "Wifi" + ConnectivityManager.TYPE_WIMAX -> "WifiMax" + ConnectivityManager.TYPE_MOBILE -> { + val telephonyManager = application.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager + if (telephonyManager.simState != TelephonyManager.SIM_STATE_READY) return "unknown" + when(telephonyManager.networkType) { + // Unknown + TelephonyManager.NETWORK_TYPE_UNKNOWN -> "Cellular - Unknown" + // Cellular Data–2G + TelephonyManager.NETWORK_TYPE_EDGE, TelephonyManager.NETWORK_TYPE_GPRS, TelephonyManager.NETWORK_TYPE_CDMA, + TelephonyManager.NETWORK_TYPE_IDEN, TelephonyManager.NETWORK_TYPE_1xRTT -> "Cellular - 2G" + // Cellular Data–3G + TelephonyManager.NETWORK_TYPE_UMTS, TelephonyManager.NETWORK_TYPE_HSDPA, TelephonyManager.NETWORK_TYPE_HSPA, + TelephonyManager.NETWORK_TYPE_HSPAP, TelephonyManager.NETWORK_TYPE_HSUPA, TelephonyManager.NETWORK_TYPE_EVDO_0, + TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyManager.NETWORK_TYPE_EVDO_B -> "Cellular - 3G" + // Cellular Data–4G + TelephonyManager.NETWORK_TYPE_LTE -> "Cellular - 4G" + else -> "Cellular - Unknown Generation" + } + + } + else -> "unknown" + } + + } + + fun getIP(): String { + return "unknown" + } + + fun getOS(): String { + return "android" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/exposure/time/Corrector.kt b/app/src/main/java/com/gh/common/exposure/time/Corrector.kt new file mode 100644 index 0000000000..6c31b79915 --- /dev/null +++ b/app/src/main/java/com/gh/common/exposure/time/Corrector.kt @@ -0,0 +1,27 @@ +package com.gh.common.exposure.time + +import com.gh.gamecenter.retrofit.RetrofitManager +import com.halo.assistant.HaloApp +import rx.schedulers.Schedulers +import kotlin.concurrent.fixedRateTimer + +class Corrector { + + companion object { + const val TIME_CORRECTOR_ADJUST_PERIOD: Long = 600000 + } + + var delta: Long = 0 + + init { + fixedRateTimer("TimeUtil-Corrector-Checker", initialDelay = 0, period = TIME_CORRECTOR_ADJUST_PERIOD) { + RetrofitManager.getInstance(HaloApp.getInstance().application).api.time + .subscribeOn(Schedulers.io()) + .subscribe({ + val serverTime = java.lang.Long.parseLong(it.string()) + delta = serverTime * 1000 - System.currentTimeMillis() + }, Throwable::printStackTrace) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/exposure/time/ServerTime.kt b/app/src/main/java/com/gh/common/exposure/time/ServerTime.kt new file mode 100644 index 0000000000..53aa4c0894 --- /dev/null +++ b/app/src/main/java/com/gh/common/exposure/time/ServerTime.kt @@ -0,0 +1,3 @@ +package com.gh.common.exposure.time + +data class ServerTime(val time: Long) \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/exposure/time/TimeUtil.kt b/app/src/main/java/com/gh/common/exposure/time/TimeUtil.kt new file mode 100644 index 0000000000..284bd59be9 --- /dev/null +++ b/app/src/main/java/com/gh/common/exposure/time/TimeUtil.kt @@ -0,0 +1,22 @@ +package com.gh.common.exposure.time + +object TimeUtil { + + private lateinit var corrector: Corrector + + fun currentTimeMillis(): Long { + return corrector.delta + System.currentTimeMillis() + } + + fun currentTime(): Int { + return ( ( corrector.delta + System.currentTimeMillis() ) / 1000 ).toInt() + } + + /** + * Must be called early then real use (for example in Application) + */ + fun init() { + corrector = Corrector() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/util/DownloadItemUtils.java b/app/src/main/java/com/gh/common/util/DownloadItemUtils.java index 6d78476e1f..75d7d4f0a7 100644 --- a/app/src/main/java/com/gh/common/util/DownloadItemUtils.java +++ b/app/src/main/java/com/gh/common/util/DownloadItemUtils.java @@ -3,6 +3,7 @@ package com.gh.common.util; import android.content.Context; import android.graphics.Color; import android.os.Message; +import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import android.support.v4.util.ArrayMap; import android.support.v7.widget.RecyclerView; @@ -11,6 +12,8 @@ import android.view.View; import android.widget.TextView; import com.gh.common.constant.Config; +import com.gh.common.exposure.ExposureEvent; +import com.gh.common.exposure.ExposureUtils; import com.gh.common.view.DownloadDialog; import com.gh.download.DownloadManager; import com.gh.gamecenter.DownloadManagerActivity; @@ -315,20 +318,22 @@ public class DownloadItemUtils { final String entrance, final String location) { + setOnClickListener(context, downloadBtn, gameEntity, position, adapter, entrance, location, null); + } + + public static void setOnClickListener(final Context context, + final TextView downloadBtn, + final GameEntity gameEntity, + final int position, + final RecyclerView.Adapter adapter, + final String entrance, + final String location, + final ExposureEvent traceEvent) { + if (gameEntity.getApk().size() == 1) { - downloadBtn.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - onNormalClick(context, downloadBtn, gameEntity, position, adapter, entrance, location); - } - }); + downloadBtn.setOnClickListener(v -> onNormalClick(context, downloadBtn, gameEntity, position, adapter, entrance, location, traceEvent)); } else { - downloadBtn.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - DownloadDialog.getInstance(context).showPopupWindow(v, gameEntity, entrance, location); - } - }); + downloadBtn.setOnClickListener(v -> DownloadDialog.getInstance(context).showPopupWindow(v, gameEntity, entrance, location, traceEvent)); } } @@ -340,31 +345,32 @@ public class DownloadItemUtils { final RecyclerView.Adapter adapter, final String entrance, final String location) { + onNormalClick(context, downloadBtn, gameEntity, position, adapter, entrance, location, null); + } + + public static void onNormalClick(final Context context, + final TextView downloadBtn, + final GameEntity gameEntity, + final int position, + final RecyclerView.Adapter adapter, + final String entrance, + final String location, + @Nullable final ExposureEvent traceEvent) { String str = downloadBtn.getText().toString(); switch (str) { case "下载": if (NetworkUtils.isWifiConnected(context)) { - download(context, gameEntity, downloadBtn, entrance, location); + download(context, gameEntity, downloadBtn, entrance, location, traceEvent); } else { - DialogUtils.showDownloadDialog(context, new DialogUtils.ConfirmListener() { - @Override - public void onConfirm() { - download(context, gameEntity, downloadBtn, entrance, location); - } - }); + DialogUtils.showDownloadDialog(context, () -> download(context, gameEntity, downloadBtn, entrance, location, traceEvent)); } break; case "插件化": if (NetworkUtils.isWifiConnected(context)) { - plugin(context, gameEntity, downloadBtn, entrance, location); + plugin(context, gameEntity, downloadBtn, entrance, location, traceEvent); } else { - DialogUtils.showDownloadDialog(context, new DialogUtils.ConfirmListener() { - @Override - public void onConfirm() { - plugin(context, gameEntity, downloadBtn, entrance, location); - } - }); + DialogUtils.showDownloadDialog(context, () -> plugin(context, gameEntity, downloadBtn, entrance, location, traceEvent)); } break; case "安装": @@ -381,14 +387,9 @@ public class DownloadItemUtils { break; case "更新": if (NetworkUtils.isWifiConnected(context)) { - update(context, gameEntity, entrance, location); + update(context, gameEntity, entrance, location, traceEvent); } else { - DialogUtils.showDownloadDialog(context, new DialogUtils.ConfirmListener() { - @Override - public void onConfirm() { - update(context, gameEntity, entrance, location); - } - }); + DialogUtils.showDownloadDialog(context, () -> update(context, gameEntity, entrance, location, traceEvent)); } break; } @@ -399,12 +400,15 @@ public class DownloadItemUtils { GameEntity gameEntity, TextView downloadBtn, String entrance, - String location) { + String location, + @Nullable ExposureEvent traceEvent) { String msg = FileUtils.isCanDownload(context, gameEntity.getApk().get(0).getSize()); if (TextUtils.isEmpty(msg)) { DataUtils.onGameDownloadEvent(context, gameEntity.getName(), gameEntity.getApk().get(0).getPlatform(), entrance, "下载开始"); - DownloadManager.createDownload(context, gameEntity, context.getString(R.string.download), entrance, location); + ExposureEvent downloadExposureEvent = ExposureUtils.INSTANCE.logADownloadExposureEvent(gameEntity, gameEntity.getApk().get(0).getPlatform(), traceEvent, ExposureUtils.DownloadType.DOWNLOAD); + + DownloadManager.createDownload(context, gameEntity, context.getString(R.string.download), entrance, location, downloadExposureEvent); Utils.toast(context, gameEntity.getName() + "已加入下载队列"); downloadBtn.setText(R.string.downloading); @@ -419,12 +423,14 @@ public class DownloadItemUtils { //插件化 private static void plugin(Context context, GameEntity gameEntity, TextView downloadBtn, String entrance, - String location) { + String location, @Nullable ExposureEvent traceEvent) { String msg = FileUtils.isCanDownload(context, gameEntity.getApk().get(0).getSize()); if (TextUtils.isEmpty(msg)) { DataUtils.onGameDownloadEvent(context, gameEntity.getName(), gameEntity.getApk().get(0).getPlatform(), entrance, "下载开始"); - DownloadManager.createDownload(context, gameEntity, "插件化", entrance, location); + ExposureEvent downloadExposureEvent = ExposureUtils.INSTANCE.logADownloadExposureEvent(gameEntity, gameEntity.getApk().get(0).getPlatform(), traceEvent, ExposureUtils.DownloadType.PLUGIN_DOWNLOAD); + + DownloadManager.createDownload(context, gameEntity, "插件化", entrance, location, downloadExposureEvent); Utils.toast(context, gameEntity.getName() + "已加入下载队列"); downloadBtn.setText(R.string.downloading); @@ -458,9 +464,10 @@ public class DownloadItemUtils { } //更新 - private static void update(Context context, GameEntity gameEntity, String entrance, String location) { + private static void update(Context context, GameEntity gameEntity, String entrance, String location, @Nullable ExposureEvent traceEvent) { DataUtils.onGameUpdateEvent(context, gameEntity.getName(), gameEntity.getApk().get(0).getPlatform(), "下载开始"); - DownloadManager.createDownload(context, gameEntity, "更新", entrance, location); + ExposureEvent downloadExposureEvent = ExposureUtils.INSTANCE.logADownloadExposureEvent(gameEntity, gameEntity.getApk().get(0).getPlatform(), traceEvent, ExposureUtils.DownloadType.UPDATE); + DownloadManager.createDownload(context, gameEntity, "更新", entrance, location, downloadExposureEvent); } } diff --git a/app/src/main/java/com/gh/common/util/EntranceUtils.java b/app/src/main/java/com/gh/common/util/EntranceUtils.java index 7ed0223fbb..beafe3019b 100644 --- a/app/src/main/java/com/gh/common/util/EntranceUtils.java +++ b/app/src/main/java/com/gh/common/util/EntranceUtils.java @@ -81,6 +81,7 @@ public class EntranceUtils { public static final String KEY_ASK_TAG = "askTag"; public static final String KEY_ASK_COLUMN_TAG = "askColumnTag"; public static final String KEY_COMMUNITY_DATA = "communityData"; + public static final String KEY_TRACE_EVENT = "trace_event"; public static final String KEY_SUBJECT_DATA = "subjectData"; public static void jumpActivity(Context context, Bundle bundle) { diff --git a/app/src/main/java/com/gh/common/util/IntentUtils.java b/app/src/main/java/com/gh/common/util/IntentUtils.java index b2dbaac4a4..5b66dac8ae 100644 --- a/app/src/main/java/com/gh/common/util/IntentUtils.java +++ b/app/src/main/java/com/gh/common/util/IntentUtils.java @@ -57,7 +57,7 @@ public class IntentUtils { return data; } - public static void startCategoryListActivity(Context context, CategoryEntity category) { - context.startActivity(CategoryListActivity.Companion.getIntent(context, category, "全部")); + public static void startCategoryListActivity(Context context, String categoryTitle, CategoryEntity category) { + context.startActivity(CategoryListActivity.Companion.getIntent(context, categoryTitle, category, "全部")); } } diff --git a/app/src/main/java/com/gh/common/view/DownloadDialog.java b/app/src/main/java/com/gh/common/view/DownloadDialog.java index 74159a305d..4c20f54b9d 100644 --- a/app/src/main/java/com/gh/common/view/DownloadDialog.java +++ b/app/src/main/java/com/gh/common/view/DownloadDialog.java @@ -1,6 +1,7 @@ package com.gh.common.view; import android.content.Context; +import android.support.annotation.Nullable; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.OnPageChangeListener; import android.view.Gravity; @@ -14,6 +15,7 @@ import android.widget.LinearLayout.LayoutParams; import android.widget.PopupWindow; import android.widget.TextView; +import com.gh.common.exposure.ExposureEvent; import com.gh.common.util.DisplayUtils; import com.gh.common.util.PackageUtils; import com.gh.common.util.PlatformUtils; @@ -84,6 +86,7 @@ public class DownloadDialog implements OnCollectionCallBackListener { private LinearLayout dialog_ll_collection_hint; private String entrance; private String location; + private ExposureEvent traceEvent; private final int row = 3; private final int column = 3; private boolean isLoadPlatform; @@ -101,6 +104,10 @@ public class DownloadDialog implements OnCollectionCallBackListener { } public void showPopupWindow(View view, GameEntity gameEntity, String entrance, String location) { + showPopupWindow(view, gameEntity, entrance, location, null); + } + + public void showPopupWindow(View view, GameEntity gameEntity, String entrance, String location, @Nullable ExposureEvent traceEvent) { if (isShow && (popupWindow == null || !popupWindow.isShowing())) { isShow = false; @@ -113,6 +120,7 @@ public class DownloadDialog implements OnCollectionCallBackListener { this.gameEntity = gameEntity; this.entrance = entrance; this.location = location; + this.traceEvent = traceEvent; gameApk = sortApk(new ArrayList<>(gameEntity.getApk())); @@ -340,7 +348,7 @@ public class DownloadDialog implements OnCollectionCallBackListener { currentItem = viewPager.getCurrentItem(); } Utils.log("currentItem = " + currentItem); - adapter = new PlatformPagerAdapter(mContext, this, gameEntity, apkList, entrance, location); + adapter = new PlatformPagerAdapter(mContext, this, gameEntity, apkList, entrance, location, traceEvent); viewPager.setAdapter(adapter); viewPager.setCurrentItem(currentItem); } @@ -428,7 +436,7 @@ public class DownloadDialog implements OnCollectionCallBackListener { addHintPoint(dialog_ll_collection_hint, size); collectionAdapter = new PlatformPagerAdapter( - mContext, null, gameEntity, gameCollectionEntity.getSaveApkEntity(), entrance, location); + mContext, null, gameEntity, gameCollectionEntity.getSaveApkEntity(), entrance, location, traceEvent); collectionViewPager.setAdapter(collectionAdapter); collectionViewPager.addOnPageChangeListener(new MyPageChangeListener(dialog_ll_collection_hint)); diff --git a/app/src/main/java/com/gh/common/view/SubCategoryView.kt b/app/src/main/java/com/gh/common/view/SubCategoryView.kt index f41feb3903..84736d5cb8 100644 --- a/app/src/main/java/com/gh/common/view/SubCategoryView.kt +++ b/app/src/main/java/com/gh/common/view/SubCategoryView.kt @@ -16,6 +16,7 @@ class SubCategoryView @JvmOverloads constructor(context: Context, attrs: Attribu var rightTv: TextView var primeCategory: CategoryEntity? = null + var categoryTitle: String? = "" init { View.inflate(context, R.layout.layout_sub_category, this) @@ -39,7 +40,7 @@ class SubCategoryView @JvmOverloads constructor(context: Context, attrs: Attribu private fun setCategory(tv: TextView, category: CategoryEntity) { tv.text = category.name - tv.setOnClickListener { tv.context.startActivity(CategoryListActivity.getIntent(tv.context, primeCategory!!, category.name!!)) } + tv.setOnClickListener { tv.context.startActivity(CategoryListActivity.getIntent(tv.context, categoryTitle!!, primeCategory!!, category.name!!)) } } } \ No newline at end of file diff --git a/app/src/main/java/com/gh/download/DownloadManager.java b/app/src/main/java/com/gh/download/DownloadManager.java index 71f035256e..297fc9cf27 100644 --- a/app/src/main/java/com/gh/download/DownloadManager.java +++ b/app/src/main/java/com/gh/download/DownloadManager.java @@ -7,8 +7,10 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.preference.PreferenceManager; +import android.support.annotation.Nullable; import android.support.v4.util.ArrayMap; +import com.gh.common.exposure.ExposureEvent; import com.gh.common.util.AppDebugConfig; import com.gh.common.util.DataCollectionUtils; import com.gh.common.util.DialogUtils; @@ -18,6 +20,7 @@ import com.gh.gamecenter.entity.ApkEntity; import com.gh.gamecenter.entity.GameEntity; import com.gh.gamecenter.eventbus.EBDownloadStatus; import com.gh.gamecenter.manager.PackageManager; +import com.google.gson.Gson; import com.lightgame.config.CommonDebug; import com.lightgame.download.ConnectionUtils; import com.lightgame.download.DataChanger; @@ -44,6 +47,7 @@ import static android.os.Build.MANUFACTURER; public class DownloadManager implements DownloadStatusListener { private static DownloadManager mInstance; + private static Gson gson = new Gson(); private Context mContext; private Handler mHandler; @@ -181,8 +185,9 @@ public class DownloadManager implements DownloadStatusListener { GameEntity gameEntity, String method, String entrance, - String location) { - createDownload(context, gameEntity.getApk().get(0), gameEntity, method, entrance, location); + String location, + @Nullable ExposureEvent traceEvent) { + createDownload(context, gameEntity.getApk().get(0), gameEntity, method, entrance, location, traceEvent); } public static void createDownload(final Context context, @@ -190,7 +195,8 @@ public class DownloadManager implements DownloadStatusListener { GameEntity gameEntity, String method, String entrance, - String location) { + String location, + @Nullable ExposureEvent traceEvent) { // 安装指引 if ("Huawei".equalsIgnoreCase(MANUFACTURER) || "Oppo".equalsIgnoreCase(MANUFACTURER)) { @@ -198,12 +204,7 @@ public class DownloadManager implements DownloadStatusListener { final SharedPreferences.Editor edit = sp.edit(); if (sp.getBoolean("InstallHint" + PackageUtils.getVersionName(context), true)) { DialogUtils.showInstallHintDialog(context, - new DialogUtils.ConfirmListener() { - @Override - public void onConfirm() { - edit.putBoolean("InstallHint" + PackageUtils.getVersionName(context), false).apply(); - } - }); + () -> edit.putBoolean("InstallHint" + PackageUtils.getVersionName(context), false).apply()); } } @@ -218,6 +219,7 @@ public class DownloadManager implements DownloadStatusListener { downloadEntity.setPackageName(apkEntity.getPackageName()); downloadEntity.setGameId(gameEntity.getId()); downloadEntity.setEntrance(entrance); + downloadEntity.setExposureTrace(gson.toJson(traceEvent)); downloadEntity.setLocation(location); downloadEntity.setVersionName(apkEntity.getVersion()); int installed = 0; diff --git a/app/src/main/java/com/gh/gamecenter/GameDetailActivity.java b/app/src/main/java/com/gh/gamecenter/GameDetailActivity.java index 7d205b7d3f..0e860e8060 100644 --- a/app/src/main/java/com/gh/gamecenter/GameDetailActivity.java +++ b/app/src/main/java/com/gh/gamecenter/GameDetailActivity.java @@ -4,6 +4,10 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; +import com.gh.common.exposure.ExposureEvent; +import com.gh.common.exposure.ExposureManager; +import com.gh.common.exposure.ExposureTraceUtils; +import com.gh.common.exposure.ExposureType; import com.gh.common.util.DataUtils; import com.gh.common.util.EntranceUtils; import com.gh.gamecenter.entity.GameEntity; @@ -15,7 +19,6 @@ import com.gh.gamecenter.gamedetail.GameDetailFragment; */ public class GameDetailActivity extends NormalActivity { - @Override protected Intent provideNormalIntent() { return getTargetIntent(this, GameDetailActivity.class, GameDetailFragment.class); @@ -47,15 +50,32 @@ public class GameDetailActivity extends NormalActivity { } /** - * @param switchToFirstTag 是否跳转到动态 tab + * 启动游戏详情页面 with 曝光事件 */ - public static void startGameDetailActivity(Context context, String gameId, String entrance, boolean switchToFirstTag) { + public static void startGameDetailActivity(Context context, GameEntity gameEntity, String entrance, ExposureEvent traceEvent) { + DataUtils.onMtaEvent(context, "详情页面", "游戏详情", gameEntity != null ? gameEntity.getName() : ""); + + ExposureEvent clickEvent = ExposureEvent.Companion.createEvent(gameEntity, traceEvent.getSource(), ExposureTraceUtils.INSTANCE.appendTrace(traceEvent), ExposureType.CLICK); + ExposureManager.INSTANCE.log(clickEvent, false); + Bundle bundle = new Bundle(); + bundle.putString(EntranceUtils.KEY_ENTRANCE, entrance); + bundle.putParcelable(GameEntity.TAG, gameEntity); + bundle.putParcelable(EntranceUtils.KEY_TRACE_EVENT, clickEvent); + context.startActivity(getTargetIntent(context, GameDetailActivity.class, GameDetailFragment.class, bundle)); + } + + /** + * 启动游戏详情页面 with 曝光事件 + */ + public static void startGameDetailActivity(Context context, String gameId, String entrance, ExposureEvent traceEvent) { + + ExposureEvent clickEvent = ExposureEvent.Companion.createEvent(new GameEntity(gameId), traceEvent.getSource(), ExposureTraceUtils.INSTANCE.appendTrace(traceEvent), ExposureType.CLICK); + ExposureManager.INSTANCE.log(clickEvent, false); + Bundle bundle = new Bundle(); bundle.putString(EntranceUtils.KEY_GAMEID, gameId); bundle.putString(EntranceUtils.KEY_ENTRANCE, entrance); - bundle.putBoolean(EntranceUtils.KEY_TARGET, switchToFirstTag); -// context.startActivity(new IntentFactory.Builder(context).setActivity(GameDetailActivity.class) -// .setFragment(GameDetailFragment.class).setArgs(bundle).build()); + bundle.putParcelable(EntranceUtils.KEY_TRACE_EVENT, clickEvent); context.startActivity(getTargetIntent(context, GameDetailActivity.class, GameDetailFragment.class, bundle)); } diff --git a/app/src/main/java/com/gh/gamecenter/MainActivity.java b/app/src/main/java/com/gh/gamecenter/MainActivity.java index 454e475c93..952bcd2ea2 100644 --- a/app/src/main/java/com/gh/gamecenter/MainActivity.java +++ b/app/src/main/java/com/gh/gamecenter/MainActivity.java @@ -29,6 +29,7 @@ import com.gh.base.AppUncaughtHandler; import com.gh.base.BaseActivity; import com.gh.base.fragment.BaseFragment_ViewPager; import com.gh.common.constant.Config; +import com.gh.common.exposure.ExposureUtils; import com.gh.common.util.ClassUtils; import com.gh.common.util.ConcernUtils; import com.gh.common.util.DataCollectionUtils; @@ -274,12 +275,16 @@ public class MainActivity extends BaseActivity { // 统计下载完成事件 private void statDoneEvent(DownloadEntity downloadEntity) { + ExposureUtils.DownloadType type; + Map kv1 = new HashMap<>(); kv1.put("版本", downloadEntity.getPlatform()); kv1.put("状态", "下载完成"); if (downloadEntity.isUpdate()) { + type = ExposureUtils.DownloadType.UPDATE; DataUtils.onEvent(MainActivity.this, "游戏更新", downloadEntity.getName(), kv1); } else { + type = ExposureUtils.DownloadType.DOWNLOAD; DataUtils.onEvent(MainActivity.this, "游戏下载", downloadEntity.getName(), kv1); } @@ -294,9 +299,12 @@ public class MainActivity extends BaseActivity { kv3.put("下载", "下载完成"); kv3.put("版本", downloadEntity.getPlatform()); kv3.put("位置", downloadEntity.getEntrance()); + type = ExposureUtils.DownloadType.PLUGIN_DOWNLOAD; DataUtils.onEvent(MainActivity.this, "插件化", downloadEntity.getName(), kv3); } + ExposureUtils.INSTANCE.logADownloadCompleteExposureEvent(new GameEntity(downloadEntity.getGameId()), downloadEntity.getPlatform(), downloadEntity.getExposureTrace(), type); + DataCollectionUtils.uploadDownload(this, downloadEntity, "完成"); } diff --git a/app/src/main/java/com/gh/gamecenter/adapter/PlatformAdapter.java b/app/src/main/java/com/gh/gamecenter/adapter/PlatformAdapter.java index d0062a2ccb..18d2e5c4a7 100644 --- a/app/src/main/java/com/gh/gamecenter/adapter/PlatformAdapter.java +++ b/app/src/main/java/com/gh/gamecenter/adapter/PlatformAdapter.java @@ -16,6 +16,8 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; +import com.gh.common.exposure.ExposureEvent; +import com.gh.common.exposure.ExposureUtils; import com.gh.common.util.DataUtils; import com.gh.common.util.DialogUtils; import com.gh.common.util.DisplayUtils; @@ -58,6 +60,7 @@ public class PlatformAdapter extends BaseRecyclerAdapter { private String mEntrance; private String mLocation; + private ExposureEvent mTraceEvent; private int mAdapterPosition; private int mCount; @@ -67,7 +70,7 @@ public class PlatformAdapter extends BaseRecyclerAdapter { public PlatformAdapter(Context context, OnCollectionCallBackListener listener, GameEntity gameEntity, CollectionCloseEntity closeEntity, ArrayMap eMap, SparseArray aMap, List list, String entrance, String location, - int position) { + int position, ExposureEvent traceEvent) { super(context); mOnCollectionCallBackListener = listener; @@ -79,6 +82,7 @@ public class PlatformAdapter extends BaseRecyclerAdapter { mEntrance = entrance; mLocation = location; mAdapterPosition = position; + mTraceEvent = traceEvent; if (mPlatformList.size() <= (mRow * mColumn)) { mCount = mPlatformList.size(); } else if (mPlatformList.size() - (mRow * mColumn) * mAdapterPosition >= (mRow * mColumn)) { @@ -126,12 +130,7 @@ public class PlatformAdapter extends BaseRecyclerAdapter { if (NetworkUtils.isWifiConnected(mContext)) { download(apkEntity, viewHolder.mDownloadItemTvStatus, "下载"); } else { - DialogUtils.showDownloadDialog(mContext, new DialogUtils.ConfirmListener() { - @Override - public void onConfirm() { - download(apkEntity, viewHolder.mDownloadItemTvStatus, "下载"); - } - }); + DialogUtils.showDownloadDialog(mContext, () -> download(apkEntity, viewHolder.mDownloadItemTvStatus, "下载")); } } else { String status = viewHolder.mDownloadItemTvStatus.getText().toString(); @@ -159,12 +158,7 @@ public class PlatformAdapter extends BaseRecyclerAdapter { if (NetworkUtils.isWifiConnected(mContext)) { download(apkEntity, viewHolder.mDownloadItemTvStatus, "插件化"); } else { - DialogUtils.showDownloadDialog(mContext, new DialogUtils.ConfirmListener() { - @Override - public void onConfirm() { - download(apkEntity, viewHolder.mDownloadItemTvStatus, "插件化"); - } - }); + DialogUtils.showDownloadDialog(mContext, () -> download(apkEntity, viewHolder.mDownloadItemTvStatus, "插件化")); } break; case "安装插件": @@ -174,12 +168,7 @@ public class PlatformAdapter extends BaseRecyclerAdapter { if (NetworkUtils.isWifiConnected(mContext)) { update(apkEntity); } else { - DialogUtils.showDownloadDialog(mContext, new DialogUtils.ConfirmListener() { - @Override - public void onConfirm() { - update(apkEntity); - } - }); + DialogUtils.showDownloadDialog(mContext, () -> update(apkEntity)); } break; } @@ -390,7 +379,9 @@ public class PlatformAdapter extends BaseRecyclerAdapter { kv6.put("版本", apkEntity.getPlatform()); DataUtils.onEvent(mContext, "插件化", mGameEntity.getName(), kv6); - DownloadManager.createDownload(mContext, apkEntity, mGameEntity, method, mEntrance, mLocation); + ExposureEvent downloadExposureEvent = ExposureUtils.INSTANCE.logADownloadExposureEvent(mGameEntity, apkEntity.getPlatform(), mTraceEvent, ExposureUtils.DownloadType.PLUGIN_DOWNLOAD); + + DownloadManager.createDownload(mContext, apkEntity, mGameEntity, method, mEntrance, mLocation, downloadExposureEvent); // DownloadManager.getInstance(mContext).putStatus(apkEntity.getUrl(), "downloading"); mDownloadItemTvStatus.setText("0.0%"); @@ -452,7 +443,9 @@ public class PlatformAdapter extends BaseRecyclerAdapter { kv.put("状态", "下载开始"); DataUtils.onEvent(mContext, "游戏更新", mGameEntity.getName(), kv); - DownloadManager.createDownload(mContext, apkEntity, mGameEntity, "更新", mEntrance, mLocation); + ExposureEvent downloadExposureEvent = ExposureUtils.INSTANCE.logADownloadExposureEvent(mGameEntity, apkEntity.getPlatform(), mTraceEvent, ExposureUtils.DownloadType.PLUGIN_UPDATE); + + DownloadManager.createDownload(mContext, apkEntity, mGameEntity, "更新", mEntrance, mLocation, downloadExposureEvent); } private GradientDrawable getGradientDrawable(int color) { diff --git a/app/src/main/java/com/gh/gamecenter/adapter/PlatformPagerAdapter.java b/app/src/main/java/com/gh/gamecenter/adapter/PlatformPagerAdapter.java index b1c77b3c0f..4bdd60ca2a 100644 --- a/app/src/main/java/com/gh/gamecenter/adapter/PlatformPagerAdapter.java +++ b/app/src/main/java/com/gh/gamecenter/adapter/PlatformPagerAdapter.java @@ -10,6 +10,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; +import com.gh.common.exposure.ExposureEvent; import com.gh.common.util.DisplayUtils; import com.gh.download.DownloadManager; import com.gh.gamecenter.entity.ApkEntity; @@ -39,13 +40,14 @@ public class PlatformPagerAdapter extends PagerAdapter { private String mEntrance; private String mLocation; + private ExposureEvent mTraceEvent; // 下载多平台显示 private final int mRow = 3; private final int mColumn = 3; public PlatformPagerAdapter(Context context, OnCollectionCallBackListener listener, GameEntity gameEntity, - List list, String entrance, String location) { + List list, String entrance, String location, ExposureEvent traceEvent) { mContext = context; mOnCollectionCallBackListener = listener; mGameEntity = gameEntity; @@ -53,6 +55,7 @@ public class PlatformPagerAdapter extends PagerAdapter { mPlatformAdapterSparseArray = new SparseArray<>(); mEntrance = entrance; mLocation = location; + mTraceEvent= traceEvent; mColseEntity = new CollectionCloseEntity(); @@ -103,7 +106,7 @@ public class PlatformPagerAdapter extends PagerAdapter { recyclerView.setOverScrollMode(View.OVER_SCROLL_NEVER); PlatformAdapter adapter = new PlatformAdapter( - mContext, mOnCollectionCallBackListener, mGameEntity, mColseEntity, mEntryMap, mPlatformAdapterSparseArray, mPlatformList, mEntrance, mLocation, position); + mContext, mOnCollectionCallBackListener, mGameEntity, mColseEntity, mEntryMap, mPlatformAdapterSparseArray, mPlatformList, mEntrance, mLocation, position, mTraceEvent); mPlatformAdapterSparseArray.put(position, adapter); recyclerView.setAdapter(adapter); diff --git a/app/src/main/java/com/gh/gamecenter/adapter/viewholder/DetailViewHolder.java b/app/src/main/java/com/gh/gamecenter/adapter/viewholder/DetailViewHolder.java index 7a0f42b011..fab1e768ed 100644 --- a/app/src/main/java/com/gh/gamecenter/adapter/viewholder/DetailViewHolder.java +++ b/app/src/main/java/com/gh/gamecenter/adapter/viewholder/DetailViewHolder.java @@ -2,9 +2,12 @@ package com.gh.gamecenter.adapter.viewholder; import android.content.Context; import android.content.Intent; +import android.support.annotation.Nullable; import android.text.TextUtils; import android.view.View; +import com.gh.common.exposure.ExposureEvent; +import com.gh.common.exposure.ExposureUtils; import com.gh.common.util.DataUtils; import com.gh.common.util.DialogUtils; import com.gh.common.util.NetworkUtils; @@ -45,6 +48,12 @@ public class DetailViewHolder { // 注意View的命名 public DetailViewHolder(View view, GameEntity gameEntity, DownloadEntity downloadEntity, String downloadOffText, String downloadAddWord, boolean isNewsDetail, String entrance, String name, String title) { + new DetailViewHolder(view, gameEntity, downloadEntity, downloadOffText, downloadAddWord, isNewsDetail, entrance, name, title, null); + } + + // 注意View的命名 + public DetailViewHolder(View view, GameEntity gameEntity, DownloadEntity downloadEntity, String downloadOffText, + String downloadAddWord, boolean isNewsDetail, String entrance, String name, String title, @Nullable ExposureEvent traceEvent) { downloadBottom = view.findViewById(R.id.detail_ll_bottom); mDownloadPb = view.findViewById(R.id.detail_progressbar); @@ -55,10 +64,11 @@ public class DetailViewHolder { this.isNewsDetail = isNewsDetail; this.context = view.getContext(); - final OnDetailDownloadClickListener listener = new OnDetailDownloadClickListener(this, entrance, name, title); + final OnDetailDownloadClickListener listener = new OnDetailDownloadClickListener(this, entrance, name, title, traceEvent); mDownloadPb.setOnClickListener(listener); } + static class OnDetailDownloadClickListener implements View.OnClickListener { private DetailViewHolder mViewHolder; private GameEntity mGameEntity; @@ -66,14 +76,16 @@ public class DetailViewHolder { private String mEntrance; private String mName; private String mTitle; + private ExposureEvent mTraceEvent; - public OnDetailDownloadClickListener(DetailViewHolder viewHolder, String entrance, String name, String title) { + public OnDetailDownloadClickListener(DetailViewHolder viewHolder, String entrance, String name, String title, ExposureEvent traceEvent) { mViewHolder = viewHolder; mGameEntity = viewHolder.gameEntity; mDownloadEntity = viewHolder.downloadEntity; mEntrance = entrance; mName = name; mTitle = title; + mTraceEvent = traceEvent; } @Override @@ -99,7 +111,7 @@ public class DetailViewHolder { } } else { DownloadDialog.getInstance(mViewHolder.context).showPopupWindow(v, mGameEntity, - StringUtils.buildString(mEntrance, "+(", mName, "[", mTitle, "])"), mName + ":" + mTitle); + StringUtils.buildString(mEntrance, "+(", mName, "[", mTitle, "])"), mName + ":" + mTitle, mTraceEvent); } break; case LAUNCH_OR_OPEN: @@ -119,6 +131,7 @@ public class DetailViewHolder { if (mDownloadEntity != null) { PackageUtils.launchSetup(mViewHolder.context, mDownloadEntity.getPath()); } + break; } } @@ -137,7 +150,14 @@ public class DetailViewHolder { if (TextUtils.isEmpty(msg)) { DataUtils.onGameDownloadEvent(mViewHolder.context, mGameEntity.getName(), apkEntity.getPlatform(), StringUtils.buildString(mEntrance, "+(", mName, "[", mTitle, "])"), "下载开始"); - DownloadManager.createDownload(mViewHolder.context, apkEntity, mGameEntity, method, StringUtils.buildString(mEntrance, "+(", mName, "[", mTitle, "])"), mName + ":" + mTitle); + ExposureEvent downloadExposureEvent = ExposureUtils.INSTANCE.logADownloadExposureEvent(mGameEntity, apkEntity.getPlatform(), mTraceEvent, ExposureUtils.DownloadType.DOWNLOAD); + DownloadManager.createDownload(mViewHolder.context, + apkEntity, + mGameEntity, + method, + StringUtils.buildString(mEntrance, "+(", mName, "[", mTitle, "])"), + mName + ":" + mTitle, + downloadExposureEvent); mViewHolder.mDownloadPb.setProgress(0); mViewHolder.mDownloadPb.setDownloadType("插件化".equals(method) ? diff --git a/app/src/main/java/com/gh/gamecenter/category/CategoryDirectoryAdapter.kt b/app/src/main/java/com/gh/gamecenter/category/CategoryDirectoryAdapter.kt index 08727106db..2e42b7cb08 100644 --- a/app/src/main/java/com/gh/gamecenter/category/CategoryDirectoryAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/category/CategoryDirectoryAdapter.kt @@ -14,7 +14,7 @@ import com.gh.gamecenter.baselist.ListAdapter import com.gh.gamecenter.databinding.ItemCategoryBinding import com.gh.gamecenter.entity.CategoryEntity -class CategoryDirectoryAdapter(context: Context) : ListAdapter(context) { +class CategoryDirectoryAdapter(context: Context, var categoryTitle: String) : ListAdapter(context) { var expandStatusMap: HashMap = HashMap() @@ -33,17 +33,19 @@ class CategoryDirectoryAdapter(context: Context) : ListAdapter(c when (holder) { is CategoryViewHolder -> { holder.binding.category = mEntityList[position] + holder.binding.title = categoryTitle holder.bindCategory( category = mEntityList[position], expandableStatusMap = expandStatusMap, isExpended = expandStatusMap[position] != null && expandStatusMap[position] == true, - marginTop = sixteenDp) + marginTop = sixteenDp, + categoryTitle = categoryTitle) } } } internal class CategoryViewHolder(var binding: ItemCategoryBinding) : RecyclerView.ViewHolder(binding.root) { - fun bindCategory(category: CategoryEntity, expandableStatusMap: HashMap, isExpended: Boolean, marginTop: Int) { + fun bindCategory(category: CategoryEntity, expandableStatusMap: HashMap, isExpended: Boolean, marginTop: Int, categoryTitle: String) { category.data?.let { var subCategoryView: SubCategoryView? = null @@ -58,6 +60,7 @@ class CategoryDirectoryAdapter(context: Context) : ListAdapter(c subCategoryView = SubCategoryView(binding.root.context) + subCategoryView?.categoryTitle = categoryTitle subCategoryView?.primeCategory = category subCategoryView?.layoutParams = params @@ -107,6 +110,7 @@ class CategoryDirectoryAdapter(context: Context) : ListAdapter(c val params = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT) params.setMargins(0, marginTop, 0, 0) + subCategoryView?.categoryTitle = categoryTitle subCategoryView?.primeCategory = category subCategoryView?.layoutParams = params diff --git a/app/src/main/java/com/gh/gamecenter/category/CategoryDirectoryFragment.kt b/app/src/main/java/com/gh/gamecenter/category/CategoryDirectoryFragment.kt index bc7e00041c..1833467f15 100644 --- a/app/src/main/java/com/gh/gamecenter/category/CategoryDirectoryFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/category/CategoryDirectoryFragment.kt @@ -19,7 +19,7 @@ class CategoryDirectoryFragment : ListFragment { +public class CategoryListAdapter extends BaseRecyclerAdapter implements IExposable { private OnRequestCallBackListener mOnRequestCallBackListener; @@ -59,14 +66,17 @@ public class CategoryListAdapter extends BaseRecyclerAdapter mExposureEventSparseArray; + public CategoryListAdapter(Context context, OnRequestCallBackListener listener, String type, String id - , String name, String entrance, String order, String tagType, boolean isOrder) { + , String name, String entrance, String order, String tagType, boolean isOrder, String title) { super(context); this.mOnRequestCallBackListener = listener; this.mType = type; @@ -76,9 +86,12 @@ public class CategoryListAdapter extends BaseRecyclerAdapter(); + mExposureEventSparseArray = new SparseArray<>(); + mSubjectList = new ArrayList<>(); mIsLoaded = false; mIsNetworkError = false; @@ -303,6 +316,13 @@ public class CategoryListAdapter extends BaseRecyclerAdapter exposureSources = new ArrayList<>(); + exposureSources.add(new ExposureSource(mTitle, mName)); + exposureSources.add(new ExposureSource("二级分类", mType + "+" + ("download:-1".equals(mOrder) ? "最热" : "最新"))); + gameEntity.setSequence(position + 1); + ExposureEvent event = ExposureEvent.Companion.createEvent(gameEntity, exposureSources, null, ExposureType.EXPOSURE); + mExposureEventSparseArray.put(position, event); + holder.itemView.setOnClickListener(v -> { Map kv = new HashMap<>(); kv.put("名字", gameEntity.getName()); @@ -312,13 +332,13 @@ public class CategoryListAdapter extends BaseRecyclerAdapter exposureSources = new ArrayList<>(); + exposureSources.add(new ExposureSource(mTitle, mName)); + exposureSources.add(new ExposureSource("二级分类", mType + "+" + ("download:-1".equals(mOrder) ? "最热" : "最新"))); + gameEntity.setSequence(position + 1); + ExposureEvent event = ExposureEvent.Companion.createEvent(gameEntity, exposureSources, null, ExposureType.EXPOSURE); + mExposureEventSparseArray.put(position, event); + holder.itemView.setOnClickListener(v -> { Map kv = new HashMap<>(); kv.put("名字", gameEntity.getName()); @@ -363,13 +390,13 @@ public class CategoryListAdapter extends BaseRecyclerAdapter getEventListByPosition(int pos) { + return null; + } } diff --git a/app/src/main/java/com/gh/gamecenter/category/CategoryListFragment.kt b/app/src/main/java/com/gh/gamecenter/category/CategoryListFragment.kt index 99dd215692..554d5c6d07 100644 --- a/app/src/main/java/com/gh/gamecenter/category/CategoryListFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/category/CategoryListFragment.kt @@ -7,6 +7,7 @@ import android.support.v7.widget.RecyclerView import android.view.View import butterknife.BindView import com.gh.base.fragment.BaseFragment +import com.gh.common.exposure.ExposureListener import com.gh.common.util.ApkActiveUtils import com.gh.common.util.DownloadItemUtils import com.gh.common.util.EntranceUtils @@ -36,15 +37,18 @@ class CategoryListFragment : BaseFragment(), OnTitleClickListener { @BindView(R.id.reuse_none_data) lateinit var mNoData: View - private var mSubjectAdapter: CategoryListAdapter? = null + private var mCategoryAdapter: CategoryListAdapter? = null private var mLayoutManager: LinearLayoutManager? = null private var mId: String? = "" private var mName: String? = "" private var mType: String? = "" private var mTagType: String? = "" + private var mTitle: String? = "" private var mListOrder: String? = "" // 列表排序 最新/最热 + private var mExposureListener: ExposureListener? = null + private var mIsOrder: Boolean = false private var mScrollTop = false @@ -53,13 +57,13 @@ class CategoryListFragment : BaseFragment(), OnTitleClickListener { // 黄壮华 添加观察者 修改2015/8/15 private val dataWatcher = object : DataWatcher() { override fun onDataChanged(downloadEntity: DownloadEntity) { - val locationList = mSubjectAdapter!!.locationMap[downloadEntity.packageName] + val locationList = mCategoryAdapter!!.locationMap[downloadEntity.packageName] if (locationList != null) { var gameEntity: GameEntity? for (location in locationList) { - gameEntity = mSubjectAdapter!!.subjectList[location] + gameEntity = mCategoryAdapter!!.subjectList[location] if (gameEntity != null) { - DownloadItemUtils.processDate(context, gameEntity, downloadEntity, mSubjectAdapter, location) + DownloadItemUtils.processDate(context, gameEntity, downloadEntity, mCategoryAdapter, location) } } } @@ -86,13 +90,14 @@ class CategoryListFragment : BaseFragment(), OnTitleClickListener { mListOrder = arguments.getString(SubjectTileFragment.KEY_LISTORDER) mTagType = arguments.getString(EntranceUtils.KEY_TAGTYPE) mEntrance = arguments.getString(EntranceUtils.KEY_ENTRANCE) + mTitle = arguments.getString(EntranceUtils.KEY_CATEGORY_TITLE) reuse_no_connection.setOnClickListener { mPbSubject.visibility = View.VISIBLE mRvSubject.visibility = View.VISIBLE reuse_no_connection.visibility = View.GONE - mSubjectAdapter = CategoryListAdapter(context, this, mType, mId, mName, mEntrance, mListOrder, mTagType, mIsOrder) - mRvSubject.adapter = mSubjectAdapter + mCategoryAdapter = CategoryListAdapter(context, this, mType, mId, mName, mEntrance, mListOrder, mTagType, mIsOrder, mTitle) + mRvSubject.adapter = mCategoryAdapter } mLayoutManager = LinearLayoutManager(context) @@ -101,8 +106,12 @@ class CategoryListFragment : BaseFragment(), OnTitleClickListener { mRvSubject.addItemDecoration(VerticalItemDecoration(context, 8, true)) mRvSubject.setHasFixedSize(true) mRvSubject.layoutManager = mLayoutManager - mSubjectAdapter = CategoryListAdapter(context, this, mType, mId, mName, mEntrance, mListOrder, mTagType, mIsOrder) - mRvSubject.adapter = mSubjectAdapter + mCategoryAdapter = CategoryListAdapter(context, this, mType, mId, mName, mEntrance, mListOrder, mTagType, mIsOrder, mTitle) + mRvSubject.adapter = mCategoryAdapter + + + mExposureListener = ExposureListener(this, mCategoryAdapter!!) + mRvSubject.addOnScrollListener(mExposureListener) mRvSubject.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) { @@ -113,9 +122,9 @@ class CategoryListFragment : BaseFragment(), OnTitleClickListener { EventBus.getDefault().post(EBReuse(SubjectTileFragment.OPEN_APPBAR)) } - if (!mSubjectAdapter!!.isRemove && mSubjectAdapter!!.isLoaded && newState == RecyclerView.SCROLL_STATE_IDLE - && mSubjectAdapter!!.itemCount == mLayoutManager!!.findLastVisibleItemPosition() + 1) { - mSubjectAdapter!!.initList(page) + if (!mCategoryAdapter!!.isRemove && mCategoryAdapter!!.isLoaded && newState == RecyclerView.SCROLL_STATE_IDLE + && mCategoryAdapter!!.itemCount == mLayoutManager!!.findLastVisibleItemPosition() + 1) { + mCategoryAdapter!!.initList(page) } } @@ -130,8 +139,8 @@ class CategoryListFragment : BaseFragment(), OnTitleClickListener { } override fun onResume() { - if (isEverPause && mSubjectAdapter != null) { - mSubjectAdapter!!.notifyDataSetChanged() + if (isEverPause && mCategoryAdapter != null) { + mCategoryAdapter!!.notifyDataSetChanged() } DownloadManager.getInstance(context).addObserver(dataWatcher) super.onResume() @@ -171,15 +180,15 @@ class CategoryListFragment : BaseFragment(), OnTitleClickListener { if ("delete" == status.status) { DownloadManager.getInstance(context).removePlatform(status.name, status.platform) - val locationList = mSubjectAdapter!!.locationMap[status.packageName] + val locationList = mCategoryAdapter!!.locationMap[status.packageName] if (locationList != null) { var gameEntity: GameEntity? for (location in locationList) { - gameEntity = mSubjectAdapter!!.subjectList[location] + gameEntity = mCategoryAdapter!!.subjectList[location] if (gameEntity != null) { gameEntity.getEntryMap().remove(status.platform) } - mSubjectAdapter!!.notifyItemChanged(location) + mCategoryAdapter!!.notifyItemChanged(location) } } } @@ -187,22 +196,22 @@ class CategoryListFragment : BaseFragment(), OnTitleClickListener { @Subscribe(threadMode = ThreadMode.MAIN) fun onEventMainThread(busFour: EBPackage) { - val locationList = mSubjectAdapter!!.locationMap[busFour.packageName] + val locationList = mCategoryAdapter!!.locationMap[busFour.packageName] if (locationList != null) { var gameEntity: GameEntity for (location in locationList) { - gameEntity = mSubjectAdapter!!.subjectList[location] + gameEntity = mCategoryAdapter!!.subjectList[location] ApkActiveUtils.filterHideApk(gameEntity) if ("安装" == busFour.type) { for (apkEntity in gameEntity.getApk()) { if (apkEntity.packageName == busFour.packageName) { gameEntity.getEntryMap().remove(apkEntity.getPlatform()) - mSubjectAdapter!!.notifyItemChanged(location) + mCategoryAdapter!!.notifyItemChanged(location) break } } } else if ("卸载" == busFour.type) { - mSubjectAdapter!!.notifyItemChanged(location) + mCategoryAdapter!!.notifyItemChanged(location) } } } @@ -216,8 +225,8 @@ class CategoryListFragment : BaseFragment(), OnTitleClickListener { mRvSubject.visibility = View.VISIBLE mPbSubject.visibility = View.VISIBLE reuse_no_connection.visibility = View.GONE - mSubjectAdapter = CategoryListAdapter(context, this, mType, mId, mName, mEntrance, mListOrder, mTagType, mIsOrder) - mRvSubject.adapter = mSubjectAdapter + mCategoryAdapter = CategoryListAdapter(context, this, mType, mId, mName, mEntrance, mListOrder, mTagType, mIsOrder, mTitle) + mRvSubject.adapter = mCategoryAdapter } } } @@ -228,14 +237,14 @@ class CategoryListFragment : BaseFragment(), OnTitleClickListener { if (InfoToolWrapperFragment.EB_NEWSFRAGMENT_TAG == busNine.from) { if (busNine.position == 0) { if (mPbSubject.visibility == View.VISIBLE) { - mSubjectAdapter!!.initList(1) + mCategoryAdapter!!.initList(1) } } } } override fun onTitleClick() { - if (mLayoutManager!!.findFirstCompletelyVisibleItemPosition() == 0 || mSubjectAdapter!!.itemCount == 0) { + if (mLayoutManager!!.findFirstCompletelyVisibleItemPosition() == 0 || mCategoryAdapter!!.itemCount == 0) { EventBus.getDefault().post(EBReuse(SubjectTileFragment.OPEN_APPBAR)) } else { mLayoutManager!!.smoothScrollToPosition(mRvSubject, null, 0) diff --git a/app/src/main/java/com/gh/gamecenter/download/GameUpdateFragmentAdapter.java b/app/src/main/java/com/gh/gamecenter/download/GameUpdateFragmentAdapter.java index 0a973d28dd..dea76de38b 100644 --- a/app/src/main/java/com/gh/gamecenter/download/GameUpdateFragmentAdapter.java +++ b/app/src/main/java/com/gh/gamecenter/download/GameUpdateFragmentAdapter.java @@ -12,6 +12,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; +import com.gh.common.exposure.ExposureUtils; import com.gh.common.util.DataCollectionUtils; import com.gh.common.util.DataUtils; import com.gh.common.util.DialogUtils; @@ -540,6 +541,7 @@ class GameUpdateFragmentAdapter extends BaseRecyclerAdapter { kv.put("版本", updateEntity.getPlatform()); kv.put("状态", "下载开始"); DataUtils.onEvent(mContext, "游戏更新", updateEntity.getName(), kv); + ExposureUtils.INSTANCE.logADownloadExposureEvent(new GameEntity(updateEntity.getId()), updateEntity.getPlatform(), null, ExposureUtils.DownloadType.UPDATE); DownloadEntity downloadEntity = new DownloadEntity(); downloadEntity.setUrl(updateEntity.getUrl()); diff --git a/app/src/main/java/com/gh/gamecenter/entity/GameEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/GameEntity.kt index bc4ff2d082..5d6f904157 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/GameEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/entity/GameEntity.kt @@ -69,6 +69,20 @@ class GameEntity : Parcelable { var des: String? = null + // 用来标记在专题中的序号,仅用于曝光记录 + var sequence: Int? = -1 + + // 用来标记平台名称,仅用于曝光记录 + var platform: String? = "" + + // 用来标记下载的类型,仅用于曝光记录 + @SerializedName("download_type") + var downloadType: String? = "" + + // 用来标记下载完成的类型,仅用于曝光记录 + @SerializedName("download_complete_type") + var downloadCompleteType: String? = "" + fun getTag(): ArrayList { if (tag == null) tag = ArrayList() if (!Config.isShowPlugin(id)) return ArrayList() @@ -146,6 +160,10 @@ class GameEntity : Parcelable { gameEntity.downloadOffText = downloadOffText gameEntity.tagStyle = tagStyle gameEntity.des = des + gameEntity.sequence = sequence + gameEntity.platform = platform + gameEntity.downloadType = downloadType + gameEntity.downloadCompleteType = downloadCompleteType return gameEntity } @@ -181,10 +199,18 @@ class GameEntity : Parcelable { dest.writeValue(this.kaifuTimeHint) dest.writeTypedList(this.tagStyle) dest.writeString(this.des) + dest.writeInt(this.sequence!!) + dest.writeString(this.platform) + dest.writeString(this.downloadType) + dest.writeString(this.downloadCompleteType) } constructor() + constructor(id: String) { + this.id = id + } + protected constructor(`in`: Parcel) { this.id = `in`.readString() this.icon = `in`.readString() @@ -215,6 +241,10 @@ class GameEntity : Parcelable { this.kaifuTimeHint = `in`.readValue(Long::class.java.classLoader) as Long? this.tagStyle = `in`.createTypedArrayList(TagStyleEntity.CREATOR) this.des = `in`.readString() + this.sequence = `in`.readInt() + this.platform = `in`.readString() + this.downloadType = `in`.readString() + this.downloadCompleteType = `in`.readString() } companion object { diff --git a/app/src/main/java/com/gh/gamecenter/game/GameFragment.kt b/app/src/main/java/com/gh/gamecenter/game/GameFragment.kt index 12fb03f781..1cb80012fe 100644 --- a/app/src/main/java/com/gh/gamecenter/game/GameFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/game/GameFragment.kt @@ -8,6 +8,7 @@ import android.support.v7.widget.DefaultItemAnimator import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.view.View +import com.gh.common.exposure.ExposureListener import com.gh.common.util.EntranceUtils import com.gh.download.DownloadManager import com.gh.gamecenter.R @@ -16,7 +17,7 @@ import com.gh.gamecenter.databinding.FragmentGameBinding import com.gh.gamecenter.entity.SubjectRecommendEntity import com.gh.gamecenter.eventbus.EBDownloadStatus import com.gh.gamecenter.eventbus.EBPackage -import com.gh.gamecenter.game.data.ItemData +import com.gh.gamecenter.game.data.GameItemData import com.gh.gamecenter.normal.NormalFragment import com.halo.assistant.HaloApp import com.lightgame.download.DataWatcher @@ -37,6 +38,12 @@ class GameFragment : NormalFragment() { private var mLayoutManager: LinearLayoutManager? = null + private var mExposureListener: ExposureListener? = null + + private var mBlockData: SubjectRecommendEntity? = null + + private var mBlockName: String = "" + private val dataWatcher = object : DataWatcher() { override fun onDataChanged(downloadEntity: DownloadEntity) { mListAdapter?.notifyItemByDownload(downloadEntity) @@ -49,12 +56,12 @@ class GameFragment : NormalFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - var blockData: SubjectRecommendEntity? = null - if (arguments != null) { - blockData = arguments!!.getParcelable(EntranceUtils.KEY_BLOCK_DATA) + arguments?.let { + mBlockData = it.getParcelable(EntranceUtils.KEY_BLOCK_DATA) + mBlockName = mBlockData?.name!! setNavigationTitle("板块详情") } - val factory = GameViewModel.Factory(HaloApp.getInstance().application, blockData) + val factory = GameViewModel.Factory(HaloApp.getInstance().application, mBlockData) mViewModel = ViewModelProviders.of(this, factory).get(GameViewModel::class.java) mViewModel?.loadStatus?.observe(this, Observer { if (it != null) { @@ -64,7 +71,7 @@ class GameFragment : NormalFragment() { } }) mViewModel?.itemDataList?.observe(this, Observer { - if (it != null) mListAdapter?.setItemDataList(it as MutableList) + if (it != null) mListAdapter?.setItemDataList(it as MutableList) }) mViewModel?.commandScrollTop?.observe(this, Observer { mLayoutManager?.smoothScrollToPosition(mBinding?.gameList, null, 0) @@ -75,7 +82,7 @@ class GameFragment : NormalFragment() { super.onViewCreated(view, savedInstanceState) mBinding = FragmentGameBinding.bind(view) mBinding?.gameRefresh?.setColorSchemeColors(ContextCompat.getColor(context!!, R.color.theme)) - mListAdapter = GameFragmentAdapter(context!!, mViewModel!!) + mListAdapter = GameFragmentAdapter(context!!, mViewModel!!, mBlockName) mLayoutManager = LinearLayoutManager(context) mBinding?.gameList?.run { @@ -91,6 +98,9 @@ class GameFragment : NormalFragment() { } }) + mExposureListener = ExposureListener(this, mListAdapter!!) + mBinding?.gameList?.addOnScrollListener(mExposureListener) + mBinding?.gameRefresh?.setOnRefreshListener { mViewModel?.loadStatus?.postValue(LoadStatus.INIT_LOADING) mViewModel?.initData() diff --git a/app/src/main/java/com/gh/gamecenter/game/GameFragmentAdapter.kt b/app/src/main/java/com/gh/gamecenter/game/GameFragmentAdapter.kt index 47d7e1d3c9..f0bfad195c 100644 --- a/app/src/main/java/com/gh/gamecenter/game/GameFragmentAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/game/GameFragmentAdapter.kt @@ -7,6 +7,7 @@ import android.support.v7.widget.DefaultItemAnimator import android.support.v7.widget.GridLayoutManager import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView +import android.text.TextUtils import android.view.View import android.view.ViewGroup import android.widget.ImageView @@ -16,15 +17,20 @@ import com.facebook.imagepipeline.image.ImageInfo import com.gh.base.OnViewClickListener import com.gh.common.constant.Config import com.gh.common.constant.ItemViewType +import com.gh.common.exposure.ExposureEvent +import com.gh.common.exposure.ExposureSource +import com.gh.common.exposure.ExposureType +import com.gh.common.exposure.IExposable import com.gh.common.util.* import com.gh.gamecenter.* import com.gh.gamecenter.adapter.ImagePagerAdapter import com.gh.gamecenter.adapter.viewholder.* import com.gh.gamecenter.baselist.LoadStatus +import com.gh.gamecenter.category.CategoryDirectoryActivity import com.gh.gamecenter.databinding.* import com.gh.gamecenter.entity.GameEntity import com.gh.gamecenter.eventbus.EBDownloadStatus -import com.gh.gamecenter.game.data.ItemData +import com.gh.gamecenter.game.data.GameItemData import com.gh.gamecenter.game.horizontal.GameHorizontalAdapter import com.gh.gamecenter.game.horizontal.GameHorizontalListViewHolder import com.halo.assistant.fragment.game.GamePluginAdapter @@ -34,7 +40,7 @@ import com.lightgame.download.DownloadEntity import java.util.* import kotlin.collections.ArrayList -class GameFragmentAdapter(context: Context, model: GameViewModel) : BaseRecyclerAdapter(context) { +class GameFragmentAdapter(context: Context, model: GameViewModel, var blockName: String) : BaseRecyclerAdapter(context), IExposable { data class GameAndPosition(val entity: GameEntity?, val position: Int) @@ -44,7 +50,7 @@ class GameFragmentAdapter(context: Context, model: GameViewModel) : BaseRecycler private var mLoadStatus: LoadStatus? = null - private var mItemDataList: MutableList = ArrayList() + private var mItemDataList: MutableList = ArrayList() private var mIsOpenPluginList: Boolean = false @@ -56,8 +62,7 @@ class GameFragmentAdapter(context: Context, model: GameViewModel) : BaseRecycler } } - - fun setItemDataList(itemDataList: MutableList) { + fun setItemDataList(itemDataList: MutableList) { mItemDataList = itemDataList.toMutableList() notifyDataSetChanged() } @@ -169,10 +174,17 @@ class GameFragmentAdapter(context: Context, model: GameViewModel) : BaseRecycler holder.binding.gameOrder.visibility = View.GONE } + val keyName = if (TextUtils.isEmpty(blockName)) "首页" else "板块" + val keyValue = if (TextUtils.isEmpty(blockName)) "" else blockName + itemData.exposureEvent = ExposureEvent.createEvent(gameEntity = gameEntity, + source = listOf(ExposureSource(keyName, keyValue), ExposureSource("专题", subjectData.name!!)), + eTrace = null, + event = ExposureType.EXPOSURE) + DownloadItemUtils.setOnClickListener(mContext, holder.binding.downloadBtn, gameEntity, position, this@GameFragmentAdapter, StringUtils.buildString("(游戏-专题:", subjectData.name, "-列表[", (position + 1).toString(), "])"), - StringUtils.buildString("游戏-专题-", subjectData.name, ":", gameEntity.name)) + StringUtils.buildString("游戏-专题-", subjectData.name, ":", gameEntity.name), itemData.exposureEvent) DownloadItemUtils.updateItem(mContext, gameEntity, GameViewHolder(holder.binding), !gameEntity.isPluggable) holder.itemView.setOnClickListener { val kv = HashMap() @@ -184,10 +196,10 @@ class GameFragmentAdapter(context: Context, model: GameViewModel) : BaseRecycler if (gameEntity.isPluggable) { GameDetailActivity.startGameDetailActivity(mContext, gameEntity.id, - StringUtils.buildString("(游戏-专题:插件化-列表[", (subjectData.position).toString(), "])")) + StringUtils.buildString("(游戏-专题:插件化-列表[", (subjectData.position).toString(), "])"), itemData.exposureEvent) } else { GameDetailActivity.startGameDetailActivity(mContext, gameEntity, - StringUtils.buildString("(游戏-专题:", subjectData.name, "-列表[", (subjectData.position).toString(), "])")) + StringUtils.buildString("(游戏-专题:", subjectData.name, "-列表[", (subjectData.position).toString(), "])"), itemData.exposureEvent) } } } else if (holder is GameViewPagerViewHolder) { @@ -202,6 +214,8 @@ class GameFragmentAdapter(context: Context, model: GameViewModel) : BaseRecycler if (entity.type == "block") { mContext.startActivity(BlockActivity.getIntent(mContext, entity)) + } else if (entity.type == "category") { + mContext.startActivity(CategoryDirectoryActivity.getIntent(mContext, entity.columnId!!, entity.name!!)) } else { SubjectActivity.startSubjectActivity(mContext, entity.columnId, entity.columnName, entity.order , StringUtils.buildString("(游戏-专题:", entity.name, "[1-", (data + 1).toString(), "]", ")")) @@ -318,7 +332,16 @@ class GameFragmentAdapter(context: Context, model: GameViewModel) : BaseRecycler }) } else if (holder is GameImageViewHolder) { val entity = mItemDataList[position].image + + val keyName = if (TextUtils.isEmpty(blockName)) "首页" else "板块" + val keyValue = if (TextUtils.isEmpty(blockName)) "" else blockName + mItemDataList[position].exposureEvent = ExposureEvent.createEvent(gameEntity = entity, + source = listOf(ExposureSource(keyName, keyValue), ExposureSource("专题", entity?.subjectData?.name + "-大图")), + eTrace = null, + event = ExposureType.EXPOSURE) + holder.binding.game = entity + holder.binding.traceEvent = mItemDataList[position].exposureEvent val name = entity?.name val link = entity?.link @@ -342,7 +365,7 @@ class GameFragmentAdapter(context: Context, model: GameViewModel) : BaseRecycler DataCollectionUtils.uploadClick(mContext, "$name-大图", "游戏-专题") when (entity.type) { - "game" -> GameDetailActivity.startGameDetailActivity(mContext, link, "(游戏-专题:$name-大图)") + "game" -> GameDetailActivity.startGameDetailActivity(mContext, link, "(游戏-专题:$name-大图)", mItemDataList[position].exposureEvent) "news" -> { // 统计阅读量 NewsUtils.statNewsViews(mContext, link) @@ -354,11 +377,27 @@ class GameFragmentAdapter(context: Context, model: GameViewModel) : BaseRecycler } }) } else if (holder is GameHorizontalListViewHolder) { - val binding = holder.binding val subjectEntity = mItemDataList[position].horizontalColumn + val exposureEventList = arrayListOf() + + subjectEntity?.data?.let { + val maxDisplayCount = if (it.size < 4) it.size else 4 + val keyName = if (TextUtils.isEmpty(blockName)) "首页" else "板块" + val keyValue = if (TextUtils.isEmpty(blockName)) "" else blockName + for (i in 0 until maxDisplayCount) { + val event = ExposureEvent.createEvent(gameEntity = it[i], + source = listOf(ExposureSource(keyName, keyValue), ExposureSource("专题", subjectEntity.name!!)), + eTrace = null, + event = ExposureType.EXPOSURE) + exposureEventList.add(event) + } + mItemDataList[position].exposureEventList = exposureEventList + } + + val binding = holder.binding var subjectAdapter = binding.horizontalRv.adapter if (subjectAdapter == null) { - subjectAdapter = GameHorizontalAdapter(mContext, subjectEntity!!) + subjectAdapter = GameHorizontalAdapter(mContext, subjectEntity!!, exposureEventList) binding.subject = subjectEntity (binding.horizontalRv.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false binding.horizontalRv.layoutManager = GridLayoutManager(mContext, 4) @@ -431,4 +470,12 @@ class GameFragmentAdapter(context: Context, model: GameViewModel) : BaseRecycler } return positionList } + + override fun getEventByPosition(pos: Int): ExposureEvent? { + return mItemDataList[pos].exposureEvent + } + + override fun getEventListByPosition(pos: Int): List? { + return mItemDataList[pos].exposureEventList + } } diff --git a/app/src/main/java/com/gh/gamecenter/game/GameViewModel.kt b/app/src/main/java/com/gh/gamecenter/game/GameViewModel.kt index c175f8fa9b..8208cb1359 100644 --- a/app/src/main/java/com/gh/gamecenter/game/GameViewModel.kt +++ b/app/src/main/java/com/gh/gamecenter/game/GameViewModel.kt @@ -13,8 +13,8 @@ import com.gh.common.util.RandomUtils import com.gh.download.DownloadManager import com.gh.gamecenter.baselist.LoadStatus import com.gh.gamecenter.entity.* +import com.gh.gamecenter.game.data.GameItemData import com.gh.gamecenter.game.data.GameSubjectData -import com.gh.gamecenter.game.data.ItemData import com.gh.gamecenter.manager.PackageManager import com.gh.gamecenter.retrofit.Response import com.gh.gamecenter.retrofit.RetrofitManager @@ -39,12 +39,12 @@ class GameViewModel(application: Application, blockData: SubjectRecommendEntity? var subjectList: MutableList = ArrayList() // 专题 var subjectDigestList = ArrayList() // 专题入口 - var itemDataList: MutableLiveData> = MutableLiveData() + var itemDataList: MutableLiveData> = MutableLiveData() val loadStatus = MutableLiveData() val mSubjectChangedMap: ArrayMap> = ArrayMap() //存储换一换的数据 var positionAndPackageMap = HashMap() // key: packageName + position, value: position - private val itemDataListCache: MutableList = ArrayList() + private val itemDataListCache: MutableList = ArrayList() val commandScrollTop = MutableLiveData() @@ -276,7 +276,7 @@ class GameViewModel(application: Application, blockData: SubjectRecommendEntity? // 轮播图+专题入口 if (blockData == null || blockData?.display?.recommend!! || blockData?.display?.slide!!) { - val itemDataTop = ItemData() + val itemDataTop = GameItemData() itemDataTop.slideList = slideList itemDataTop.subjectRecommend = subjectDigestList itemDataListCache.add(itemDataTop) @@ -284,7 +284,7 @@ class GameViewModel(application: Application, blockData: SubjectRecommendEntity? // 插件化 if (pluginList != null && pluginList!!.isNotEmpty()) { - val itemDataPlugin = ItemData() + val itemDataPlugin = GameItemData() itemDataPlugin.pluginList = pluginList itemDataListCache.add(itemDataPlugin) @@ -299,14 +299,15 @@ class GameViewModel(application: Application, blockData: SubjectRecommendEntity? if (data == null || data.isEmpty()) continue if (!data[0].image.isNullOrEmpty()) { - val itemDataImage = ItemData() + val itemDataImage = GameItemData() itemDataImage.image = data[0] + itemDataImage.image?.subjectData = GameSubjectData(name = subjectEntity.name, isOrder = false) itemDataListCache.add(itemDataImage) if (data[0].type == "game") addGamePositionAndPackage(data[0]) } if (subjectEntity.type == "game_horizontal") { - val itemDataSubject = ItemData() + val itemDataSubject = GameItemData() itemDataSubject.horizontalColumn = subjectEntity itemDataListCache.add(itemDataSubject) for (game in subjectEntity.data!!) { @@ -316,7 +317,7 @@ class GameViewModel(application: Application, blockData: SubjectRecommendEntity? continue } - val itemDataHead = ItemData() + val itemDataHead = GameItemData() itemDataHead.columnHead = subjectEntity itemDataListCache.add(itemDataHead) @@ -327,7 +328,8 @@ class GameViewModel(application: Application, blockData: SubjectRecommendEntity? game.subjectData = GameSubjectData(subjectEntity.name, subjectEntity.tag, i + if (data[0].image.isNullOrEmpty()) 1 else 0, subjectEntity.isOrder) - val itemDataGame = ItemData() + val itemDataGame = GameItemData() + game.sequence = i itemDataGame.game = game itemDataListCache.add(itemDataGame) addGamePositionAndPackage(game) diff --git a/app/src/main/java/com/gh/gamecenter/game/data/GameItemData.kt b/app/src/main/java/com/gh/gamecenter/game/data/GameItemData.kt index 68c7bef5c5..bf17dac588 100644 --- a/app/src/main/java/com/gh/gamecenter/game/data/GameItemData.kt +++ b/app/src/main/java/com/gh/gamecenter/game/data/GameItemData.kt @@ -1,11 +1,12 @@ package com.gh.gamecenter.game.data +import com.gh.common.exposure.ExposureEvent import com.gh.gamecenter.entity.GameEntity import com.gh.gamecenter.entity.SlideEntity import com.gh.gamecenter.entity.SubjectEntity import com.gh.gamecenter.entity.SubjectRecommendEntity -class ItemData { +class GameItemData { var game: GameEntity? = null var columnHead: SubjectEntity? = null var horizontalColumn: SubjectEntity? = null @@ -13,4 +14,6 @@ class ItemData { var image: GameEntity? = null var pluginList: List? = null var slideList: List? = null + var exposureEvent: ExposureEvent? = null + var exposureEventList: List? = null } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/game/data/GameSubjectData.kt b/app/src/main/java/com/gh/gamecenter/game/data/GameSubjectData.kt index 249706ab94..364735b681 100644 --- a/app/src/main/java/com/gh/gamecenter/game/data/GameSubjectData.kt +++ b/app/src/main/java/com/gh/gamecenter/game/data/GameSubjectData.kt @@ -1,3 +1,3 @@ package com.gh.gamecenter.game.data -data class GameSubjectData(val name: String?, val tag: String?, val position: Int?, val isOrder: Boolean) +data class GameSubjectData(val name: String? = "", val tag: String? = "", val position: Int? = 0, val isOrder: Boolean) diff --git a/app/src/main/java/com/gh/gamecenter/game/horizontal/GameHorizontalAdapter.kt b/app/src/main/java/com/gh/gamecenter/game/horizontal/GameHorizontalAdapter.kt index dd307d0cbe..fc59987b96 100644 --- a/app/src/main/java/com/gh/gamecenter/game/horizontal/GameHorizontalAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/game/horizontal/GameHorizontalAdapter.kt @@ -2,6 +2,7 @@ package com.gh.gamecenter.game.horizontal import android.content.Context import android.view.ViewGroup +import com.gh.common.exposure.ExposureEvent import com.gh.common.util.StringUtils import com.gh.gamecenter.GameDetailActivity import com.gh.gamecenter.R @@ -9,7 +10,7 @@ import com.gh.gamecenter.databinding.GameHorizontalItemBinding import com.gh.gamecenter.entity.SubjectEntity import com.lightgame.adapter.BaseRecyclerAdapter -class GameHorizontalAdapter(context: Context, subject: SubjectEntity) : BaseRecyclerAdapter(context) { +class GameHorizontalAdapter(context: Context, subject: SubjectEntity, var exposureEventList: ArrayList) : BaseRecyclerAdapter(context) { private val mSubjectEntity: SubjectEntity = subject private var mIndex = 0 @@ -36,7 +37,7 @@ class GameHorizontalAdapter(context: Context, subject: SubjectEntity) : BaseRecy holder?.binding?.executePendingBindings() holder?.itemView?.setOnClickListener({ GameDetailActivity.startGameDetailActivity(mContext, gameEntity, - StringUtils.buildString("(游戏-专题:", mSubjectEntity.name, "-列表[", (position + 1).toString(), "])")) + StringUtils.buildString("(游戏-专题:", mSubjectEntity.name, "-列表[", (position + 1).toString(), "])"), exposureEventList[position]) }) } diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/GameDetailFragment.java b/app/src/main/java/com/gh/gamecenter/gamedetail/GameDetailFragment.java index ce6ad16051..06d581f501 100644 --- a/app/src/main/java/com/gh/gamecenter/gamedetail/GameDetailFragment.java +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/GameDetailFragment.java @@ -16,6 +16,7 @@ import android.widget.TextView; import com.facebook.drawee.view.SimpleDraweeView; import com.gh.base.BaseActivity; +import com.gh.common.exposure.ExposureEvent; import com.gh.common.util.ApkActiveUtils; import com.gh.common.util.CheckLoginUtils; import com.gh.common.util.ConcernUtils; @@ -107,6 +108,7 @@ public class GameDetailFragment extends NormalFragment { private GameEntity mGameEntity; private DownloadEntity mDownloadEntity; private GameDetailEntity mGameDetailEntity; + private ExposureEvent mTraceEvent; private String mGameId; private String downloadAddWord; @@ -148,7 +150,7 @@ public class GameDetailFragment extends NormalFragment { // 每次获取需要重新创建, 防止数据刷新 //TODO 更改为监听数据刷新,哪有每次new对象的 return new DetailViewHolder(mCachedView, mGameEntity, mDownloadEntity, downloadOffText, downloadAddWord, - false, mEntrance, getString(R.string.title_game_detail), title); // 下载按钮ViewHolder + false, mEntrance, getString(R.string.title_game_detail), title, mTraceEvent); // 下载按钮ViewHolder } @Override @@ -177,6 +179,7 @@ public class GameDetailFragment extends NormalFragment { if (mGameId == null) { mGameEntity = args.getParcelable(GameEntity.TAG); + mTraceEvent = args.getParcelable(EntranceUtils.KEY_TRACE_EVENT); if (mGameEntity != null) { mGameId = mGameEntity.getId(); // setNavigationTitle(mGameEntity.getName()); diff --git a/app/src/main/java/com/gh/gamecenter/subject/SubjectAdapter.java b/app/src/main/java/com/gh/gamecenter/subject/SubjectAdapter.java index 971f20f022..2e9f011de6 100644 --- a/app/src/main/java/com/gh/gamecenter/subject/SubjectAdapter.java +++ b/app/src/main/java/com/gh/gamecenter/subject/SubjectAdapter.java @@ -6,11 +6,16 @@ import android.support.v4.util.ArrayMap; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.ViewHolder; import android.text.TextUtils; +import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; import com.gh.base.OnRequestCallBackListener; import com.gh.common.constant.ItemViewType; +import com.gh.common.exposure.ExposureEvent; +import com.gh.common.exposure.ExposureSource; +import com.gh.common.exposure.ExposureType; +import com.gh.common.exposure.IExposable; import com.gh.common.util.ApkActiveUtils; import com.gh.common.util.DataCollectionUtils; import com.gh.common.util.DataUtils; @@ -38,6 +43,8 @@ import com.gh.gamecenter.retrofit.RetrofitManager; import com.lightgame.adapter.BaseRecyclerAdapter; import com.lightgame.utils.Utils; +import org.jetbrains.annotations.Nullable; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -45,11 +52,10 @@ import java.util.Map; import retrofit2.HttpException; import rx.android.schedulers.AndroidSchedulers; -import rx.functions.Func1; import rx.schedulers.Schedulers; -class SubjectAdapter extends BaseRecyclerAdapter { +class SubjectAdapter extends BaseRecyclerAdapter implements IExposable { private OnRequestCallBackListener mOnRequestCallBackListener; @@ -69,6 +75,8 @@ class SubjectAdapter extends BaseRecyclerAdapter { private boolean mIsOrder; private boolean mIsLoaded; + private SparseArray mExposureEventSparseArray; + SubjectAdapter(Context context, OnRequestCallBackListener listener, String type, String id , String name, String entrance, String order, String tagType, boolean isOrder) { super(context); @@ -82,6 +90,7 @@ class SubjectAdapter extends BaseRecyclerAdapter { this.mTagType = tagType; mLocationMap = new ArrayMap<>(); + mExposureEventSparseArray = new SparseArray<>(); mSubjectList = new ArrayList<>(); mIsLoaded = false; @@ -97,12 +106,7 @@ class SubjectAdapter extends BaseRecyclerAdapter { RetrofitManager.getInstance(mContext).getApi().getColumn(mId , UrlFilterUtils.getFilterQuery("publish", mOrder) , UrlFilterUtils.getFilterQuery("type", mType), page) - .map(new Func1, List>() { - @Override - public List call(List list) { - return removeDuplicateData(mSubjectList, list); - } - }) + .map(list -> removeDuplicateData(mSubjectList, list)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Response>() { @@ -237,32 +241,29 @@ class SubjectAdapter extends BaseRecyclerAdapter { holder.gameDes.setVisibility(View.VISIBLE); holder.gameDes.setText(gameEntity.getDes()); } - holder.itemView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Map kv = new HashMap<>(); - kv.put("名字", gameEntity.getName()); - kv.put("位置", "头图"); - DataUtils.onEvent(mContext, "点击", mName, kv); + holder.itemView.setOnClickListener(v -> { + Map kv = new HashMap<>(); + kv.put("名字", gameEntity.getName()); + kv.put("位置", "头图"); + DataUtils.onEvent(mContext, "点击", mName, kv); - DataCollectionUtils.uploadClick(mContext, "头图", mName); + DataCollectionUtils.uploadClick(mContext, "头图", mName); - switch (gameEntity.getType()) { - case "game": - GameDetailActivity.startGameDetailActivity(mContext, gameEntity.getLink(), mEntrance + "(" + mName + ":大图)"); - break; - case "news": - // 统计阅读量 - NewsUtils.statNewsViews(mContext, gameEntity.getLink()); - Intent intent = NewsDetailActivity.getIntentById(mContext, gameEntity.getLink(), mEntrance + "(" + mName + ":大图)"); + switch (gameEntity.getType()) { + case "game": + GameDetailActivity.startGameDetailActivity(mContext, gameEntity.getLink(), mEntrance + "(" + mName + ":大图)"); + break; + case "news": + // 统计阅读量 + NewsUtils.statNewsViews(mContext, gameEntity.getLink()); + Intent intent = NewsDetailActivity.getIntentById(mContext, gameEntity.getLink(), mEntrance + "(" + mName + ":大图)"); - mContext.startActivity(intent); - break; - case "column": - SubjectActivity.startSubjectActivity(mContext, gameEntity.getLink(), gameEntity.getName(), false - , mEntrance + "(" + mName + ":大图)"); - break; - } + mContext.startActivity(intent); + break; + case "column": + SubjectActivity.startSubjectActivity(mContext, gameEntity.getLink(), gameEntity.getName(), false + , mEntrance + "(" + mName + ":大图)"); + break; } }); } @@ -317,25 +318,29 @@ class SubjectAdapter extends BaseRecyclerAdapter { holder.initServerType(gameEntity, mContext); GameViewUtils.setLabelList(mContext, holder.gameLabelList, gameEntity.getTag(), mTagType, gameEntity.getTagStyle()); - holder.itemView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Map kv = new HashMap<>(); - kv.put("名字", gameEntity.getName()); - kv.put("位置", String.valueOf(position + 1)); - DataUtils.onEvent(mContext, "点击", mName, kv); + ArrayList exposureSources = new ArrayList<>(); + exposureSources.add(new ExposureSource("专题", mName)); + exposureSources.add(new ExposureSource("专题详情", mType + "+" + ("latest".equals(mOrder) ? "最新" : "最热"))); + gameEntity.setSequence(position + 1); + ExposureEvent event = ExposureEvent.Companion.createEvent(gameEntity, exposureSources, null, ExposureType.EXPOSURE); + mExposureEventSparseArray.put(position, event); - DataCollectionUtils.uploadClick(mContext, "列表", mName, gameEntity.getName()); + holder.itemView.setOnClickListener(v -> { + Map kv = new HashMap<>(); + kv.put("名字", gameEntity.getName()); + kv.put("位置", String.valueOf(position + 1)); + DataUtils.onEvent(mContext, "点击", mName, kv); - GameDetailActivity.startGameDetailActivity(mContext, gameEntity, - StringUtils.buildString(mEntrance, "+(", mName, ":列表[", mType, "=", ("latest".equals(mOrder) ? "最新" : "最热"), "=", String.valueOf(position + 1), "])")); - } + DataCollectionUtils.uploadClick(mContext, "列表", mName, gameEntity.getName()); + + GameDetailActivity.startGameDetailActivity(mContext, gameEntity, + StringUtils.buildString(mEntrance, "+(", mName, ":列表[", mType, "=", ("latest".equals(mOrder) ? "最新" : "最热"), "=", String.valueOf(position + 1), "])"), event); }); DownloadItemUtils.setOnClickListener(mContext, holder.gameDownloadBtn, gameEntity, position, this, StringUtils.buildString(mEntrance, "+(", mName, ":列表[", mType, "=", ("latest".equals(mOrder) ? "最新" : "最热"), "=", String.valueOf(position + 1), "])"), - StringUtils.buildString(mName, ":", gameEntity.getName())); + StringUtils.buildString(mName, ":", gameEntity.getName()), event); DownloadItemUtils.updateItem(mContext, gameEntity, holder, true); } @@ -371,19 +376,23 @@ class SubjectAdapter extends BaseRecyclerAdapter { String type = gameEntity.getTest().getType(); KaiFuUtils.setKaiFuType(holder.gameTestType, type); - holder.itemView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Map kv = new HashMap<>(); - kv.put("名字", gameEntity.getName()); - kv.put("位置", String.valueOf(holder.getPosition() + 1)); - DataUtils.onEvent(mContext, "点击", mName, kv); + ArrayList exposureSources = new ArrayList<>(); + exposureSources.add(new ExposureSource("专题", mName)); + exposureSources.add(new ExposureSource("专题详情", mType + "+" + ("latest".equals(mOrder) ? "最新" : "最热"))); + gameEntity.setSequence(position + 1); + ExposureEvent event = ExposureEvent.Companion.createEvent(gameEntity, exposureSources, null, ExposureType.EXPOSURE); + mExposureEventSparseArray.put(position, event); - DataCollectionUtils.uploadClick(mContext, "列表", mName, gameEntity.getName()); + holder.itemView.setOnClickListener(v -> { + Map kv = new HashMap<>(); + kv.put("名字", gameEntity.getName()); + kv.put("位置", String.valueOf(holder.getPosition() + 1)); + DataUtils.onEvent(mContext, "点击", mName, kv); - GameDetailActivity.startGameDetailActivity(mContext, gameEntity, - StringUtils.buildString(mEntrance, "+(", mName, ":列表[", mType, "=", ("latest".equals(mOrder) ? "最新" : "最热"), "=", String.valueOf(position + 1), "])")); - } + DataCollectionUtils.uploadClick(mContext, "列表", mName, gameEntity.getName()); + + GameDetailActivity.startGameDetailActivity(mContext, gameEntity, + StringUtils.buildString(mEntrance, "+(", mName, ":列表[", mType, "=", ("latest".equals(mOrder) ? "最新" : "最热"), "=", String.valueOf(position + 1), "])"), event); }); // if (gameEntity.getTest().getEnd() != 0) { @@ -398,7 +407,7 @@ class SubjectAdapter extends BaseRecyclerAdapter { DownloadItemUtils.setOnClickListener(mContext, holder.gameDownloadBtn, gameEntity, position, SubjectAdapter.this, StringUtils.buildString(mEntrance, "+(", mName, ":列表[", mType, "=", ("latest".equals(mOrder) ? "最新" : "最热"), "=", String.valueOf(position + 1), "])"), - StringUtils.buildString(mName, ":", gameEntity.getName())); + StringUtils.buildString(mName, ":", gameEntity.getName()), event); DownloadItemUtils.updateItem(mContext, gameEntity, holder, true); } @@ -446,5 +455,15 @@ class SubjectAdapter extends BaseRecyclerAdapter { return mIsRemove; } + @Nullable + @Override + public ExposureEvent getEventByPosition(int pos) { + return mExposureEventSparseArray.get(pos); + } + @Nullable + @Override + public List getEventListByPosition(int pos) { + return null; + } } diff --git a/app/src/main/java/com/gh/gamecenter/subject/SubjectListFragment.java b/app/src/main/java/com/gh/gamecenter/subject/SubjectListFragment.java index a9e27ece7c..e6da3cedf2 100644 --- a/app/src/main/java/com/gh/gamecenter/subject/SubjectListFragment.java +++ b/app/src/main/java/com/gh/gamecenter/subject/SubjectListFragment.java @@ -8,6 +8,7 @@ import android.support.v7.widget.RecyclerView; import android.view.View; import com.gh.base.fragment.BaseFragment; +import com.gh.common.exposure.ExposureListener; import com.gh.common.util.ApkActiveUtils; import com.gh.common.util.DownloadItemUtils; import com.gh.common.util.EntranceUtils; @@ -60,6 +61,8 @@ public class SubjectListFragment extends BaseFragment implements OnTitleClickLis private String mTagType; private String mListOrder; // 列表排序 最新/最热 + private ExposureListener mExposureListener; + private boolean mIsOrder; private boolean mScrollTop = false; @@ -106,15 +109,12 @@ public class SubjectListFragment extends BaseFragment implements OnTitleClickLis mTagType = arguments.getString(EntranceUtils.KEY_TAGTYPE); mEntrance = arguments.getString(EntranceUtils.KEY_ENTRANCE); - reuse_no_connection.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mPbSubject.setVisibility(View.VISIBLE); - mRvSubject.setVisibility(View.VISIBLE); - reuse_no_connection.setVisibility(View.GONE); - mSubjectAdapter = new SubjectAdapter(getContext(), SubjectListFragment.this, mType, mId, mName, mEntrance, mListOrder, mTagType, mIsOrder); - mRvSubject.setAdapter(mSubjectAdapter); - } + reuse_no_connection.setOnClickListener(v -> { + mPbSubject.setVisibility(View.VISIBLE); + mRvSubject.setVisibility(View.VISIBLE); + reuse_no_connection.setVisibility(View.GONE); + mSubjectAdapter = new SubjectAdapter(getContext(), SubjectListFragment.this, mType, mId, mName, mEntrance, mListOrder, mTagType, mIsOrder); + mRvSubject.setAdapter(mSubjectAdapter); }); mLayoutManager = new LinearLayoutManager(getContext()); @@ -126,6 +126,9 @@ public class SubjectListFragment extends BaseFragment implements OnTitleClickLis mSubjectAdapter = new SubjectAdapter(getContext(), this, mType, mId, mName, mEntrance, mListOrder, mTagType, mIsOrder); mRvSubject.setAdapter(mSubjectAdapter); + mExposureListener = new ExposureListener(this, mSubjectAdapter); + mRvSubject.addOnScrollListener(mExposureListener); + mRvSubject.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { diff --git a/app/src/main/java/com/halo/assistant/HaloApp.java b/app/src/main/java/com/halo/assistant/HaloApp.java index cdc683aaf6..90ac2d202c 100644 --- a/app/src/main/java/com/halo/assistant/HaloApp.java +++ b/app/src/main/java/com/halo/assistant/HaloApp.java @@ -10,6 +10,7 @@ import com.facebook.drawee.backends.pipeline.Fresco; import com.gh.base.GHActivityLifecycleCallbacksImpl; import com.gh.base.GHUmengNotificationClickHandler; import com.gh.common.constant.Config; +import com.gh.common.exposure.ExposureManager; import com.gh.common.util.DataUtils; import com.gh.common.util.StringUtils; import com.gh.common.util.TokenUtils; @@ -92,6 +93,8 @@ public class HaloApp extends TinkerAppLike { //初始化Fresco Fresco.initialize(getApplication()); + ExposureManager.INSTANCE.init(getApplication()); + try { //初始化友盟推送 UMConfigure.init(getApplication(), diff --git a/app/src/main/res/layout/game_horizontal_item.xml b/app/src/main/res/layout/game_horizontal_item.xml index 8689748a88..4aebd8c7fe 100644 --- a/app/src/main/res/layout/game_horizontal_item.xml +++ b/app/src/main/res/layout/game_horizontal_item.xml @@ -7,6 +7,10 @@ + + + + - + + + + +