项目中接入了穿山甲广告,其中包括图文广告和视频广告,后期发现一个偶现的问题,就是看完激励视频广告后,点击视频页面的关闭按钮,此时回到了app源生页面,UI卡住了,本来看完视频有个加分操作,现在没了。排查了代码,看看是不是自己的代码有漏洞,看了半天也没发现,然后就想,是不是穿山甲sdk的视频有问题,点击关闭按钮没有给客户端回调?为了验证这个问题,我写了个点击事件,请求激励视频并且播放,在穿山甲的回调地方添加log日志,看看是否有打印日志。由于bug是偶现的,那就是说需要大量的尝试,如果向上面的那样操作,点击一下请求广告,视频播放结束显示关闭按钮,再点击一下,比较麻烦,并且把一个人给定死在这里,需要不停的操作,能不能省点力?
第一步,我把请求广告的点击事件,放到了 Activity 的 onResume() 方法中,这样,刚进入该页面,或者视频看完点击关闭按钮后又回到该页面,都会触发激励视频广告,为了体验更好一点,我在 onResume() 方法中延迟了1秒去请求广告,并在请求广告的时候添加请求log日志;
第二步,我想在激励视频结束时也打印一个log日志,由于激励视频页面是SDK内部的Activity,没办法直接监听,我就从 Application 入手,它有个注册方法 registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) 这个方法,可以通过回调 onActivityDestroyed(Activity activity) 方法获取到 Activity 关闭的日志,由于激励视频 Activity 的名字是 "TTRewardVideoActivity" 或 "TTRewardExpressVideoActivity",所以判断 activity 的名字是这两个中的任何一个时,就打印广告关闭日志;
第三步,上一步操作能打印日志,是需要视频播放完了点击关闭按钮,能自动点击吗?激励视频页面是SDK内部的Activity,我们这边拿不到布局和点击事件,所以还是从上面的回调入手,但我们是在 onActivityResumed(Activity activity) 中做操作。知道了 activity 就可以 findViewById(),问题是现在也不知道关闭按钮的id;使用 Android Studio 自带的 Layout Inspector 工具来看激励视频页面的布局,视频播放完成后,点击这个工具,找到关闭按钮的id : tt_video_ad_close,它父容器是 tt_video_ad_close_layout;问题又来了,由于它是sdk内部的,使用 activity 的 findViewById() 的时候,找不到 id 对应的 R ,所以失败了;换个角度,还可以使用反射来找到id对应的view;使用 Resources 的 getIdentifier() 方法来获取对应的id;同时我也看看sdk内部 TTRewardVideoActivity 的源码,虽然被混淆了,但还是找到了具有关闭功能的按钮是 tt_video_ad_close_layout。然后就是在 onActivityResumed(Activity activity) 中做延迟31秒的事件,找到关闭按钮对应的view,调用 view.performClick() ,这样激励视频就自动关闭了;
第四步, 上述三步后,我就可以运行代码,让它自己停的播放视频广告,自动关闭,然后再播放,再关闭,循环执行,我只需要半个小时后看看日志就行了,如果sdk回调的日志比着广告页面关闭的日志少,就可以证明是sdk内部关闭按钮有问题了。但上面一个广告30秒,如果想再快一点,我们把 onActivityResumed(Activity activity) 延迟的时间,由 31 秒 调整到 5 秒,这样就大大提高了效率。
/**
* 反射获取view的工具类
*/
class Utils{
private static Resources mResources = null;
private static String mPackageName = null;
public static int getIdentifier(Context context, String name) {
return getIdentifier(context, name, "id");
}
private static int getIdentifier(Context context, String name, String defType) {
if (mResources == null) {
mResources = context.getResources();
}
return mResources.getIdentifier(name, defType, getPackageName(context));
}
private static String getPackageName(Context context) {
if (mPackageName == null) {
mPackageName = context.getPackageName();
}
return mPackageName;
}
}
跑了200次,发现确实是sdk内部关闭按钮的问题。怎么解决呢?我就想既然sdk内部没有给回调,那么我就手动加一个回调。我用了观察者模式,在写回调的类上实现 Observer 这个接口,然后再 Application.ActivityLifecycleCallbacks 的 onActivityDestroyed(Activity activity) 方法中判断是否是激励视频,然后发送一条信息,Observer 接到信息后,调用sdk的回调方法。细节注意:如果sdk内部已经回调了,观察者就不能再调用了,会重复,所以在sdk回调方法中把该观察者模式给移除掉,并且把回调置空,就样就不会重复执行了;还有个小细节,如果看完激励视频A,瞬间又触发了一个激励视频广告B,此时B的Activity已经打开,A的观察者关闭信息刚发过来,这时候会误判执行B广告的回调,所以可以在观察者接收消息的地方,加个时间判断,大于10秒的再执行。
class ApplicationListener implements Application.ActivityLifecycleCallbacks{
Handler mHandler = new Handler();
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
interface VideoListener{
void closeListener(int code); // 0 失败,1 成功, 2 错误
}
class VideoListenerImpl implements VideoListener, Observer{
final static long TIME_MARK = 10 * 1000;
VideoListener mListener;
long mStartTime = SystemClock.elapsedRealtime();
public VideoListenerImpl(VideoListener listener) {
this.mListener = listener;
}
@Override
public void closeListener(int code) {
if(mListener != null){
mListener.closeListener(code);
}
NotifyManager.getNotifyManager().deleteObserver(this);
mListener = null;
Log.e("RewardVideoCSJ", "onRewardVerify: " + code);
}
@Override
public void update(Observable o, Object arg) {
if (isRewardVideo(arg)) {
Log.e("RewardVideoCSJ", "update: ");
boolean valid = SystemClock.elapsedRealtime() - mStartTime > TIME_MARK;
if(valid){
closeListener(1);
}
}
}
private boolean isRewardVideo(Object arg) {
// 通过msg判断,是否是穿山甲激励视频结束后,Application 的监听回调发送的信息
return true;
}
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityStarted(final Activity activity) {
String name = activity.getClass().getSimpleName();
if ("TTRewardVideoActivity".equals(name) || "TTRewardExpressVideoActivity".equals(name)) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
View view = activity.findViewById(Utils.getIdentifier(activity, "tt_video_ad_close_layout"));
if (view != null) {
view.performClick();
}
}
}, 31 * 1000);
}
}
@Override
public void onActivityDestroyed(Activity activity) {
String name = activity.getClass().getSimpleName();
if ("TTRewardVideoActivity".equals(name) || "TTRewardExpressVideoActivity".equals(name)) {
// 发送关闭消息
Log.e("RewardVideoCSJ", "onActivityDestroyed: " + name +" " + activity.hashCode());
}
}
}
来源:CSDN
作者:Death_Huimie
链接:https://blog.csdn.net/Deaht_Huimie/article/details/104035669