第一次搞SDK的开发,简直是一脸懵逼,逻辑层的代码实现还好说,可是如何搭建SDK的框架呢,总不能撸一个app的 框架
给SDK用吧,SDK本身要考虑的东西也很多,首先网络层 、整体架构、crash统计、事件统计,代码安全,热更 等问题不断
的涌现,当然最好的老师就是撸一遍 三方的SDK看他们如何做的,主要有头条和 广点通。头条主要是教会了我 使用interface
的编程思想,广点通呢主要是动态下发dex文件达到修复的效果,本文做以总结!
github 地址 里面的sdblibrary就是头条的部分源码https://github.com/comerss/NewFrame.git
头条的广告给到我们的全是接口,所以如果顺着接口进去,那只能看到一堆方法,啥也没有,这样做的好处就是我们从接入层面很难看到源码是怎么实现的,实现类我们根本不知道在哪,于是我用笨办法反编译过来,然后把混淆后的代码一点点还原,还原了一部分,但是想要的逻辑都是能找得到的。这个包名是我随便命名的,这个无所谓,一下分析只是本人学习SDK架构的样例,请勿用做非法商业途径。
一下我们拿头条的开屏逻辑为例查看一下源码。
首先我们拿到的回调是TTSplashAd
public abstract interface TTSplashAd
{
@NonNull
public abstract View getSplashView();
public abstract int getInteractionType();
public abstract void setSplashInteractionListener(AdInteractionListener paramAdInteractionListener);
public abstract void setDownloadListener(TTAppDownloadListener paramTTAppDownloadListener);
public abstract void setNotAllowSdkCountdown();
public static abstract interface AdInteractionListener
{
public abstract void onAdClicked(View paramView, int paramInt);
public abstract void onAdShow(View paramView, int paramInt);
public abstract void onAdSkip();
public abstract void onAdTimeOver();
}
}
我们首选寻找的肯定是这个类的实现
public class TTSplashAdImpl implements TTSplashAd, MineHandler.OnResult {
private static String TAG = "TTSplashAdImpl";
private int b = 3;
private Context mContext;
private NativeAdData d;
private SplashLayout mSplashLayout;
private TTSplashAd.AdInteractionListener mInteractionListener;
private boolean g;
private MineHandler mHandler = new MineHandler(Looper.getMainLooper(), this);
private DownLoadListenerImpl mLoadListener;
TTSplashAdImpl(@NonNull Context paramContext, @NonNull NativeAdData paramh) {
this.mContext = paramContext;
this.d = paramh;
a();
}
void a() {
this.mSplashLayout = new SplashLayout(this.mContext);
AdEvent.addEventData(this.d);
if (this.d.s() <= 0) {
a(3);
} else {
this.b = this.d.s();
a(this.b);
}
c();
}
void a(Drawable paramDrawable) {
this.mSplashLayout.setDrawable(paramDrawable);
}
private void c() {
//这个方法的主要功能是对开屏广告的展示以及显示时间进行监听以及回调,尔后我们再进一步进行分析
this.mLoadListener = getDownLoadListenerImpl(this.d);
SplashManager splashManager = new SplashManager(this.mContext, this.mSplashLayout);
splashManager.setAdType(3);
this.mSplashLayout.addView(splashManager);
splashManager.setCallback(new SplashManager.SplashListener() {
public void onWindowFocusChanged(boolean paramAnonymousBoolean) {
if (mLoadListener != null) {
if (paramAnonymousBoolean) {
mLoadListener.e();
} else {
mLoadListener.f();
}
}
}
public void onAttachedToWindow() {
}
public void onDetachedFromWindow() {
}
public void onShow(View paramAnonymousView) {
AdEvent.show(mContext, d, "splash_ad");
if (!g) {
mHandler.sendEmptyMessage(1);
}
if (mInteractionListener != null) {
mInteractionListener.onAdShow(mSplashLayout, d.c());
}
if (d.t()) {
ToolUtils.a(d, paramAnonymousView);
}
LogUtils.b(d.b(), "开屏广告展示");
}
});
splashManager.setNeedCheckingShow(true);
//这个类是所有的广告的点击事件的处理类,这样就方便了全部广告的点击集中处理
AdClickListerReal adClickListerReal = new AdClickListerReal(this.mContext, this.d, "splash_ad", 4);
adClickListerReal.setView(this.mSplashLayout);
adClickListerReal.b(this.mSplashLayout.getDislikeView());
adClickListerReal.setDownLoadListener(this.mLoadListener);
adClickListerReal.setOnClickLister(new AdClickListenerImpl.OnClick() {
public void onClick(View paramAnonymousView, int paramAnonymousInt) {
if (mInteractionListener != null) {
mInteractionListener.onAdClicked(paramAnonymousView, paramAnonymousInt);
}
if ((paramAnonymousInt != 4) && (paramAnonymousInt != -1)) {
mHandler.removeCallbacksAndMessages(null);
b = 0;
}
}
});
if (this.mLoadListener != null) {
this.mLoadListener.a(new com.bytedance.sdk.openadsdk.core.a.c(this.mContext, this.d, "splash_ad"));
}
this.mSplashLayout.setOnClickListenerInternal(adClickListerReal);
this.mSplashLayout.setOnTouchListenerInternal(adClickListerReal);
this.mSplashLayout.setSkipListener(new View.OnClickListener() {
public void onClick(View paramAnonymousView) {
if (!StringUtils.isEmpty(TTSplashAdImpl.this.d.o())) {
AdEvent.c(TTSplashAdImpl.this.d);
}
if (TTSplashAdImpl.this.mInteractionListener != null) {
TTSplashAdImpl.this.mHandler.removeCallbacksAndMessages((Object) null);
TTSplashAdImpl.this.b = 0;
TTSplashAdImpl.this.mInteractionListener.onAdSkip();
}
}
});
}
@NonNull
public View getSplashView() {
return this.mSplashLayout;
}
public int getInteractionType() {
return this.d == null ? -1 : this.d.c();
}
public void setSplashInteractionListener(TTSplashAd.AdInteractionListener paramAdInteractionListener) {
this.mInteractionListener = paramAdInteractionListener;
}
public void setDownloadListener(TTAppDownloadListener paramTTAppDownloadListener) {
if (this.mLoadListener != null) {
this.mLoadListener.a(paramTTAppDownloadListener);
}
}
private DownLoadListenerImpl getDownLoadListenerImpl(NativeAdData paramh) {
if (paramh.c() == 4) {
//如果是下载类型则为其创建下载监听器
return new DownLoadListenerImpl(this.mContext, paramh, "splash_ad");
}
return null;
}
public void setNotAllowSdkCountdown() {
this.g = true;
this.mSplashLayout.setSkipIconVisibility(8);
this.mHandler.removeCallbacksAndMessages(null);
}
private void a(int paramInt) {
SpannableStringBuilder localSpannableStringBuilder = new SpannableStringBuilder(paramInt + "ViewWather | 跳过");
localSpannableStringBuilder.setSpan(new ForegroundColorSpan(this.mContext.getResources().getColor(R.color.tt_skip_red)), 0, 2, 33);
this.mSplashLayout.setSkipText(localSpannableStringBuilder);
}
public void doResult(Message paramMessage) {
if (paramMessage.what == 1) {
this.b -= 1;
if (this.b == 0) {
//几个回调 倒计时结束
if (this.mInteractionListener != null) {
this.mInteractionListener.onAdTimeOver();
}
LogUtils.b(TAG, "播放时间到");
this.mHandler.removeCallbacksAndMessages(null);
} else if (this.b > 0) {
a(this.b);
this.mHandler.sendEmptyMessageDelayed(1, 1000L);
}
}
}
}
public class SplashManager extends View {
private boolean b;
private boolean needCheckShow;
private SplashListener mSplashListener;
private View splshView;
private List<View> clickViews;
@Nullable
private List<View> crativeViews;
private boolean isAttach;
private int i;
//头条广告的View分为了几类 clickViews crativeViews
protected final Handler mHandler = new Handler(Looper.getMainLooper()) {
public void handleMessage(Message paramAnonymousMessage) {
switch (paramAnonymousMessage.what) {
case 1:
if (b) {
if (ViewHelper.a(splshView, 20, i)) {
c();
mHandler.sendEmptyMessageDelayed(2, 1000L);
if (mSplashListener != null) {
mSplashListener.onShow(splshView);
}
} else {
mHandler.sendEmptyMessageDelayed(1, 1000L);
}
}
break;
case 2:
boolean bool = ToolUtils.d(n.a(), n.a().getPackageName());
if (!ViewHelper.a(splshView, 20, i) && bool) {
if (!isAttach) {
setNeedCheckingShow(true);
}
} else {
mHandler.sendEmptyMessageDelayed(2, 1000L);
}
break;
}
}
};
public SplashManager(Context paramContext, View paramView) {
super(n.a());
this.splshView = paramView;
setLayoutParams(new ViewGroup.LayoutParams(0, 0));
}
@Override
public void onWindowFocusChanged(boolean paramBoolean) {
super.onWindowFocusChanged(paramBoolean);
if (this.mSplashListener != null) {
this.mSplashListener.onWindowFocusChanged(paramBoolean);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
b();
this.isAttach = false;
if (this.mSplashListener != null) {
this.mSplashListener.onAttachedToWindow();
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
c();
this.isAttach = true;
if (this.mSplashListener != null) {
this.mSplashListener.onDetachedFromWindow();
}
}
public void setRefClickViews(List<View> paramList) {
this.clickViews = paramList;
}
public void setRefCreativeViews(@Nullable List<View> paramList) {
this.crativeViews = paramList;
}
//这个是为了防止用户给View设置点击事件的,
public void noClick() {
onClick(this.clickViews, null);
onClick(this.crativeViews, null);
}
//所有的可点击的View集合都设置上 点击事件
public void onClick(List<View> paramList, AdClickListener paramd) {
if (ListUtils.checkEmpty(paramList)) {
for (View localView : paramList) {
localView.setOnClickListener(paramd);
localView.setOnTouchListener(paramd);
}
}
}
void b() {
if ((!this.needCheckShow) || (this.b)) {
return;
}
this.b = true;
this.mHandler.sendEmptyMessage(1);
}
void c() {
if (!this.b) {
return;
}
this.mHandler.removeCallbacksAndMessages(null);
this.b = false;
}
public void setNeedCheckingShow(boolean paramBoolean) {
this.needCheckShow = paramBoolean;
if ((!paramBoolean) && (this.b)) {
c();
} else if ((paramBoolean) && (!this.b)) {
b();
}
}
public void setCallback(SplashListener parama) {
this.mSplashListener = parama;
}
public void setAdType(int paramInt) {
this.i = paramInt;
}
public interface SplashListener {
public abstract void onWindowFocusChanged(boolean paramBoolean);
public abstract void onAttachedToWindow();
public abstract void onDetachedFromWindow();
public abstract void onShow(View paramView);
}
}
public interface SplashListener {
public abstract void onWindowFocusChanged(boolean paramBoolean);
public abstract void onAttachedToWindow();
public abstract void onDetachedFromWindow();
public abstract void onShow(View paramView);
}
我们来看上面Handler的回调的里面的这段代码
if (b) {
//所有广告的曝光逻辑计算都是在这里ViewHelper就是检测当前的广告每个一秒钟是否符合了曝光的条件,如何符合了那么就回调广告展示,
//否则就需要进行二次检测 一秒后再次检测,这里的i主要是区分开屏还是信息流,对展示的空间大小有校验的。
if (ViewHelper.a(splshView, 20, i)) {
c();
mHandler.sendEmptyMessageDelayed(2, 1000L);
if (mSplashListener != null) {
mSplashListener.onShow(splshView);
}
} else {
mHandler.sendEmptyMessageDelayed(1, 1000L);
}
}
通过以上我们可以确定了,SplashManager主要作用就是监听广告的展示时间,虽然是View但是并没有布局什么的,可以说就是个看不见的View,这个逻辑我们清楚了,那再继续看她的点击事件的处理方法。处理点击事件的类是AdClickListerReal
public class AdClickListerReal extends AdClickListenerImpl {
private boolean a = true;
public AdClickListerReal(@NonNull Context var1, @NonNull NativeAdData var2, @NonNull String var3, int var4) {
super(var1, var2, var3, var4);
}
public void a(boolean var1) {
this.a = var1;
}
public void b(View var1, int var2, int var3, int var4, int var5) {
if (this.mContext != null) {
//
int var6 = this.mAdData.c();
switch(var6) {
case 2:
case 3:
//各种统计事件,点击处理还得继续往上找。
if (this.mAdData.p() == 5) {
AdEvent.a(this.mContext, "click_button", this.mAdData, this.h, this.d, true);
}
aa.a(true);
// boolean var9 = aa.OnClick(this.bee, this.VideoManager, this.TTBannerAdImpl, this.LogUtils, this.mTTFeedAd, mR.OnClick(this.TTBannerAdImpl), this.mM);
if (this.a) {
AdEvent.a(this.mContext, "click", this.mAdData, this.h, this.d, true);
}
break;
case 4:
if (this.mDownLoadListener != null) {
this.mDownLoadListener.c();
if (this.a && this.mDownLoadListener.a()) {
AdEvent.a(this.mContext, "click", this.mAdData, this.h, this.d, true);
}
}
break;
case 5: break;
default:
var6 = -1;
}
if (this.mOnClick != null) {
this.mOnClick.onClick(var1, var6);
}
}
}
public class AdClickListenerImpl extends AdClickListener {
protected Context mContext;
protected NativeAdData mAdData;
protected String d;
protected int e;
protected WeakReference<View> mWeakReference;
protected WeakReference<View> mReference;
protected com.bytedance.sdk.openadsdk.core.nibuguan.c h;
protected OnClick mOnClick;
protected TTFeedAd mTTFeedAd;
protected com.bytedance.sdk.openadsdk.core.video.a.c k;
protected boolean l = false;
protected DownLoadListenerImpl mDownLoadListener;
public void setDownLoadListener(DownLoadListenerImpl var1) {
this.mDownLoadListener = var1;
}
public void setTTFeedAd(TTFeedAd paramTTFeedAd) {
this.mTTFeedAd = paramTTFeedAd;
}
public void b(boolean paramBoolean) {
this.l = paramBoolean;
}
public void a(com.bytedance.sdk.openadsdk.core.video.a.c paramc) {
this.k = paramc;
}
public AdClickListenerImpl(@NonNull Context paramContext, @NonNull NativeAdData paramh, @NonNull String paramString, int paramInt) {
this.mContext = paramContext.getApplicationContext();
this.mAdData = paramh;
this.d = paramString;
this.e = paramInt;
}
//这是我们设置监听的地方,
public void setOnClickLister(OnClick parama) {
this.mOnClick = parama;
}
public void setView(View paramView) {
this.mWeakReference = new WeakReference(paramView);
}
public void b(View paramView) {
this.mWeakReference = new WeakReference(paramView);
}
//这个是View点击事件真正执行地方,并回调回去了。
public void b(View paramView, int paramInt1, int paramInt2, int paramInt3, int paramInt4) {
if (this.mContext == null) {
return;
}
this.h = a(paramInt1, paramInt2, paramInt3, paramInt4, this.mQ, this.mR, this.mWeakReference == null ? null :
(View) this.mWeakReference.get(), this.mReference == null ? null : (View) this.mReference.get());
if (this.mOnClick != null) {
this.mOnClick.onClick(paramView, -1);
}
///* 86 */ boolean bool = aa.OnClick(this.bee, this.VideoManager, this.TTBannerAdImpl, this.LogUtils, this.mTTFeedAd, mR.OnClick(this.TTBannerAdImpl));
AdEvent.a(this.mContext, "click", this.mAdData, this.h, this.d, true);
}
protected com.bytedance.sdk.openadsdk.core.nibuguan.c a(int paramInt1, int paramInt2, int paramInt3, int paramInt4, long paramLong1, long paramLong2, View paramView1, View paramView2) {
return (new c.Builder()).d(paramInt1).c(paramInt2).b(paramInt3).a(paramInt4).b(paramLong1).a(paramLong2).b(ViewWather.getLocationOnScreen(paramView1)).a(ViewWather.getLocationOnScreen(paramView2)).c(ViewWather.getViewSize(paramView1)).d(ViewWather.getViewSize(paramView2)).a();
}
public interface OnClick {
void onClick(View paramView, int paramInt);
}
}
看了头条的广告逻辑其实心里有那么一点谱了,但是啊老大不仅要功能,还要腾讯广告的dex动态下发,那也没办法啊,只能去撸广点通的源码。
在安卓里面我们知道有热更新 有插件化都可以达到热更的目的,然而做SDK的时候我们能做到的热更 无非就是 使用壳儿来加载真正的逻辑,两个是独立分开的,通过DexClassLoader加载以达到热更的效果,但是缺陷就是不能实时修复,需要冷启动,第二就是反射比较耗时,我测过平均反射时间都是在100毫秒+ 的时间,各有利弊的。那我们看一下广点通的思路,从中获取一点启发。
public class PM {
private final Context a;
private String b;
private File c;
private int d;
private DexClassLoader e;
private RandomAccessFile f;
private FileLock g;
private boolean h;
private PM.a i;
private PM.a.a j = new PM.a.a() {
public final void a() {
PM.a(PM.this);
}
public final void b() {
PM.this.e();
}
//静态加载插件文件,一下其他几个是相关的配置文件
static File a(Context var0) {
return new File(var0.getDir("e_qq_com_plugin", 0), "gdt_plugin.jar");
}
static File b(Context var0) {
return new File(var0.getDir("e_qq_com_plugin", 0), "gdt_plugin.next");
}
static File c(Context var0) {
return new File(var0.getDir("e_qq_com_plugin", 0), "gdt_plugin.jar.sig");
}
static File d(Context var0) {
return new File(var0.getDir("e_qq_com_plugin", 0), "gdt_plugin.next.sig");
}
};
//大家都会用的用hashMap来做缓存,为了不每次都反射耗时。
private static final Map<Class<?>, String> k = new HashMap<Class<?>, String>() {
{
this.put(POFactory.class, "com.qq.e.comm.plugin.POFactoryImpl");
}
};
public PM(Context var1, PM.a var2) {
this.a = var1.getApplicationContext();
this.i = var2;
this.h = this.d();
if (this.b()) {
this.a();
}
}
private void a() {
GDTLogger.d("PluginFile:\t" + (this.c == null ? "null" : this.c.getAbsolutePath()));
if (this.b != null) {
try {
//创建DexClassLoader后面就用来进行加载插件。
this.e = new DexClassLoader(this.c.getAbsolutePath(), this.a.getDir("e_qq_com_dex", 0).getAbsolutePath(), (String)null, this.getClass().getClassLoader());
if (this.i != null) {
this.i.onLoadSuccess();
}
} catch (Throwable var2) {
GDTLogger.e("exception while init plugin class loader", var2);
this.e();
}
} else {
this.e = null;
}
}
private boolean d() {
try {
Context var1 = this.a;
File var3;
if (!(var3 = new File(var1.getDir("e_qq_com_plugin", 0), "update_lc")).exists()) {
var3.createNewFile();
StringUtil.writeTo("lock", var3);
}
if (!var3.exists()) {
return false;
} else {
this.f = new RandomAccessFile(var3, "rw");
this.g = this.f.getChannel().tryLock();
if (this.g != null) {
this.f.writeByte(37);
return true;
} else {
return false;
}
}
} catch (Throwable var2) {
return false;
}
}
public String getLocalSig() {
return this.b;
}
public void update(String var1, String var2) {
if (this.h) {
com.qq.e.comm.managers.plugin.a var3;
(var3 = new com.qq.e.comm.managers.plugin.a(this.a)).a(this.j);
var3.a(var1, var2);
}
}
//利用泛型来加载想要加载的类
public <T> T getFactory(Class<T> var1) throws b {
GDTLogger.d("GetFactoryInstaceforInterface:" + var1);
DexClassLoader var2 = this.e;
GDTLogger.d("PluginClassLoader is parent" + (this.getClass().getClassLoader() == var2));
if (var2 == null) {
throw new b("Fail to init GDTADPLugin,PluginClassLoader == null;while loading factory impl for:" + var1);
} else {
try {
String var3;
if (StringUtil.isEmpty(var3 = (String)k.get(var1))) {
throw new b("factory implemention name is not specified for interface:" + var1.getName());
} else {
Class var5;
//反射加载单例模式的类
Method var7 = (var5 = var2.loadClass(var3)).getDeclaredMethod("getInstance");
Object var6 = var1.cast(var7.invoke(var5));
GDTLogger.d("ServiceDelegateFactory =" + var6);
return var6;
}
} catch (Throwable var4) {
throw new b("Fail to getfactory implement instance for interface:" + var1.getName(), var4);
}
}
}
//这个就是最好的加载的例子了。
public POFactory getPOFactory() throws b {
return (POFactory)this.getFactory(POFactory.class);
}
public int getPluginVersion() {
return this.d;
}
public interface a {
void onLoadSuccess();
void onLoadFail();
public interface a {
void a();
void b();
}
}
}
那我们可以脑补一下空白,他就是利用这种利用DexClassLoader加载外部文件,那我们需要的无非就两个东西,一个是一个可以加载外部类的壳子,另一个就是真正的代码逻辑所在,初始化的时候吧外部dex加载进来。当然,我们的架构设计的时候是根据自己的需求设计而定,并没有一个严格的条框,刚开始的时候这些技术我都知道但是根本不知道怎么下手,撸了几遍SDK的代码之后,才撸了一套框架。