背景与主要方针

背景介绍

商业侧广告,广告的完结当然是在ad组件完结。 但都需求将其它事务方的界面作为载体去呈现。

例如一个彩蛋广告(完结在ad组件),是悬浮在主页信息流之上(feed组件),因而不可避免的,会侵入feed组件的代码。

因而一般的组件化开发模式下,需求feed组件依靠ad_api,由ad_api供给相关广告的接口,在feed侧调用,完结交互。

如下伪代码:

/**
* 主页信息流 (位于feed组件)  
*/  
public classMainPageFragmentextendsBaseFeedFragment {  
//彩蛋接口,位于ad组件  
privateIAdEggiAdEgg;  
@Override  
public voidonCreate(@NullableBundle savedInstanceState) {  
  super.onCreate(savedInstanceState);  
  //经过组件化路由,取得IAdEgg的完结类  
  InstanceProvider.optional(IAdEgg.class).ifPresent(it -> {  
    iAdEgg= it;  
});  
}  
@Override  
public voidonResume() {  
  super.onResume();  
  //展现彩蛋  
  iAdEgg.show();  
}  
@Override  
public voidonStop() {  
  super.onStop();  
  //彩蛋消失  
  iAdEgg.dismiss();  
}  
@Override  
public voidonDestroy() {  
  super.onDestroy();  
  //彩蛋毁掉  
  iAdEgg.destroy();  
}  
}

这样做,在事务上,没有啥问题,实际上,初期咱们也是这样处理的。

当然了,这样做的坏处,也很明显。

比如,我需求对这个广告的调用进行修正,要将原来 onStop()处履行的iAdEgg.dismiss() 办法,调整到 onPause()里边去,不可避免的,我需求改动feed组件。

再比如,咱们需求新增一个广告,比如增一个浮层广告吧,那会怎么写呢? 伪代码如下:

/**
* 主页信息流  
*/  
public classMainPageFragmentextendsBaseFeedFragment {  
//彩蛋接口,位于ad组件  
privateIAdEggiAdEgg;  
//浮层广告接口,位于ad组件  
privateIAdFloatiAdFloat;  
@Override  
public voidonCreate(@NullableBundle savedInstanceState) {  
  super.onCreate(savedInstanceState);  
  //经过组件化路由,取得IAdEgg的完结类  
  InstanceProvider.optional(IAdEgg.class).ifPresent(it -> {  
    iAdEgg= it;  
});  
//经过组件化路由,取得IAdFloat的完结类  
  InstanceProvider.optional(IAdFloat.class).ifPresent(it -> {  
    iAdFloat= it;  
});  
}  
@Override  
public voidonResume() {  
  super.onResume();  
  //展现彩蛋  
  iAdEgg.show();  
  //展现浮层广告  
  iAdFloat.show();  
}  
@Override  
public voidonStop() {  
  super.onStop();  
  //彩蛋消失  
  iAdEgg.dismiss();  
  //浮层广告消失  
  iAdFloat.dismiss();  
}  
@Override  
public voidonDestroy() {  
  super.onDestroy();  
  //彩蛋毁掉  
  iAdEgg.destroy();  
  //浮层广告毁掉  
  iAdFloat.destroy();  
}  
}

跟着商业化的特形广告越来越多,会将ad的代码不断的在feed组件进行叠加。

到现在停止,一个宿主界面最多会时承载5种特形广告。而且任何的调用机遇的修正,亦或许新增特形广告,本质上都是对feed侧进行修正。

且实际上的广告事务不会向上述伪代码一样简单明了,我方的侵入逻辑甚至增加了主页的保护成本。

定论简而言之,ad组件过度的侵入了feed组件。解耦已经火烧眉毛。

方针&完结效果

针对上述的坏处,设计解耦模型 「Geoffrey」,该模型以APT+动态代理为基础,采用接口注解的计划,必定程度 上完结了 ad组件与其它组件的解耦。

依然以彩蛋广告为例,仅需求在feed侧进行一个注册,就能监听到生命周期:

/**
* 主页信息流 位于feed组件  
*/  
public classMainPageFragmentextendsBaseFeedFragment {  
@Override  
public voidonCreate(@NullableBundle savedInstanceState) {  
  super.onCreate(savedInstanceState);  
  //经过组件化路由,取得商业行为搜集器  
  InstanceProvider.optional(IAdBehaviorDispatcher.class).ifPresent(it -> {  
     it.register(this);  
});  
}  
}

在ad组件内部完结彩蛋广告的编写

/**
* 主页彩蛋 位于ad组件  
*/   
@AddServiceHost({"com.zhihu.android.app.feed.ui.fragment.MainPageFragment"})  
@ClassImplementation(type = "AdEgg",relativeInterface = RelativeClassPath.AD_RELATIVE_PATH)  
public classAdEggDemoimplementsIAdEggObserver {  
@Override  
public voidonHostDestroy(String fromPage) {  
  //...毁掉逻辑  
}  
@Override  
public voidonHostStop(String fromPage) {  
  //...dismiss逻辑  
}  
@Override  
public voidonHostResume(String fromPage) {  
  //...show逻辑  
}  
}

因为在AdEggDemo的类注解中,运用@AddServiceHost添加了需求监听的宿主界面的全类名:MainPageFragment

因而,当MainPageFragment在其onCreate中调用it.register(this)办法,就会动态创立一个AdEggDemo的实例。

当MainPageFragment依次调用了 onResume()、onStop()、onDestroy() 时,相应的,会调用AdEggDemo的实例的 onHostResume(String fromPage)、onHostStop(String fromPage)、onHostDestroy(String fromPage),很明显,参数fromPage便是MainPageFragment的全类名,用于某些事务下的判断。

而且在宿主调用了onDestroy时,也会自动毁掉创立的AdEggDemo实例。


那么,现在咱们需求在MainPageFragment 新增一个浮层广告呢?

不需求再改动feed组件。

只需求在ad组件中新增一个AddFloat完结类,其注解同彩蛋,监听MainPageFragment。

@AddServiceHost({"com.zhihu.android.app.feed.ui.fragment.MainPageFragment"})
@ClassImplementation(type = "AdFloat", relativeInterface = RelativeClassPath.AD_RELATIVE_PATH)
public class AdFloat implements IAdFloatObserver {
    @Override
    public void onHostDestroy(String fromPage) {
        //...毁掉逻辑
    }
    @Override
    public void onHostStop(String fromPage) {
        //...dismiss逻辑
    }
    @Override
    public void onHostResume(String fromPage) {
        //...show逻辑
    }
}

也便是说,当MainPageFragment 的实例在履行生命周期办法时,会既回调 AdFloat实例里的生命周期,也回调AdEggDemo实例里的生命周期。 因为这两个类,都在类注解上参加了对MainPageFragment的监听。

从上面的例子能够看出,其实「Geoffrey」所做的事情,便是建立终究的履行类与宿主的联系,将你需求关怀的,宿主的行为,如生命周期,传递到履行类中去!**

有同学又要问了,诶诶诶,你这个计划里,如同只能传递生命周期办法,假如要传递其它行为呢?比如我要监听这个界面的有一个改写操作,我需求监听这个动作,该怎么做?

当然能够,只是feed需求这么写:

/**
* 主页信息流  
*/  
public classMainPageFragmentextendsBaseFeedFragment {  
//商业侧行为搜集器  
privateIBehaviorReceiverbehaviorReceiver;  
@Override  
public voidonCreate(@NullableBundle savedInstanceState) {  
  super.onCreate(savedInstanceState);  
  //经过组件化路由,取得商业行为搜集器  
  InstanceProvider.optional(IAdBehaviorDispatcher.class).ifPresent(it -> {  
    behaviorReceiver= it.register(this);  
});  
}  
@Override  
protected voidonRefresh(booleanfromUser) {  
  super.onRefresh(fromUser);  
  //调一下onRefresh  
  behaviorReceiver.onRefresh(fromUser);  
}  
}

到这里停止,又多了一个类 IBehaviorReceiver ,这又个啥?

这是个接口类,里边界说了,商业侧广告所需求搜集的全部行为!其间当然就包含了生命周期,只是你只要调用了注册办法,就默许会监听生命周期,不再需求自己去写。

看一下已经存在的一些办法:

/**
* 接口界说类  
* 在这里界说了商业侧需求搜集的行为  
*/  
@BehaviorCreator  
public interfaceIBehaviorReceiverextendsIHostLifecycle {  
@AddBehaviorObserver({"AdFloat"})  
voidonRefresh(booleanfromUser);  
@Override  
voidonHostCreate();  
@AddBehaviorObserver({"AdFloat"})  
@Override  
voidonHostDestroy();  
@Override  
voidonHostPause();  
@AddBehaviorObserver({"AdFloat"})  
@Override  
voidonHostResume();  
@Override  
voidonHostStart();  
@Override  
voidonHostStop();  
}

某一个类型的广告,比如浮层广告, 需求重视的宿主界面行为有:onResume、onDestroy、onRefresh 三个办法,只需求在这三个办法上的AddBehaviorObserver注解中,参加自己的一个String类型的type即可! 如上。

这个注解有啥含义呢? 含义在于,经过apt,会在编译期生成一个新接口,而且这个接口,只含有这三个办法。生成的接口如下:

@ServiceType(
value ="AdFloat",  
relativeInterface ="com.zhihu.android.behavior.IBehaviorReceiver"  
)  
public interfaceIAdFloatObserverextendsIObserver {  
voidonRefresh(booleanfromUser,String fromPage);  
voidonHostDestroy(String fromPage);  
voidonHostResume(String fromPage);  
}

咱们终究真实的履行类,完结这个接口即可!能够往上面翻翻看看用法,是不是浮层广告的履行类完结它了?然后在完结类上打好注解,那就能完结监听了。 或许咱们这里再看一遍:

@AddServiceHost({"com.zhihu.android.app.feed.ui.fragment.MainPageFragment"})
@ClassImplementation(type = "AdFloat", relativeInterface = RelativeClassPath.AD_RELATIVE_PATH)
public class AdFloat implements IAdFloatObserver {
    @Override
    public void onHostDestroy(String fromPage) {
        //...毁掉逻辑
    }
    @Override
    public void onRefresh(boolean fromUser,String fromPage) {
        //...改写逻辑
    }
    @Override
    public void onHostResume(String fromPage) {
        //...show逻辑
    }
}

总结一下,这套模型,是一个 「行为搜集分发器」,其实是将需求观察到的feed的行为,收敛到位于ad组件的「Geoffrey」中,再由「Geoffrey」进行分发到对应的ad完结中去!

优点在于,我只需求搜集一次行为,就能够屡次分发,满足商业广告中的事务特色。

行为是怎么搜集呢? 是经过在feed中运用IBehaviorReceiver这个接口去搜集。

行为怎么分发呢? 履行类中的注解上,添加了相应的宿主界面全类名。 当宿主调用了该履行为需求重视的办法时(生命周期办法 or 指定的办法),就会进行相应的回调。

到此,根本用法全部讲完。

架构描述

知乎商业化团队Android侧组件解耦模型「Geoffrey」介绍

关键:

1.经过在接口上打注解,生成了新的接口,经过apt完结,具体运用 javapoet 辅助完结该事务,有兴趣的同学能够自行了解该部份完结,这里暂且不表:

git.in.zhihu.com/yanfang/beh…

这部份代码,只与 生成接口 这项事务有关。

2.如何分发。

运用时已经讲到了,ad组件实际上,只会供给一个接口给其它组件运用,便是:IBehaviorReceiver ,其它组件经过持有该接口的引用,去自动调用ad组件需求观察的办法。

但是该接口没有任何的直接完结类! 有没有觉得很眼熟?

是的,聪明的你必定发现了,这不便是retrofit中对动态代理的运用吗? 在接口办法上参加注解,在动态代理中去让真实的履行类履行,真实的履行类便是完结了apt生成的接口的类。

先看一张运用了「Geoffrey」的类图,这是商业侧现在特形广告运用「Geoffrey」模型的类图,其间「Geoffrey」的内部完结在下面类图中为黑盒。

知乎商业化团队Android侧组件解耦模型「Geoffrey」介绍

能够比较明显的看到,feed组件现在只依靠ad_api组件,而且只持有经过IAdBehaviorDispatcher中返回的IBehaviorReceiver接口。

当feed组件经过IBehaviorReceiver调用具体办法时,「Geoffrey」会将该动作,传递到关怀此动作的履行类中去!