Android项目架构设计深入浅出
一 引导
二 项目架构演进
1 单项目阶段
2 抽象基础库阶段
代码的版本控制问题,为保证项目加快迭代,团队新招1-3名开发同学,多人同时在一个项目上开发时,Git代码合并总会出现冲突,非常影响开发效率; 项目的编译构建问题,随着项目代码量逐渐增多,运行App都是基于源码编译,以至于首次整包编译构建的速度逐渐变慢,甚至会出现为了验证一行代码的改动而需要等待大几分钟或者更久时间的现象; 多应用的代码复用问题,公司可能同时在进行多个App的开发,同样的代码总是需要通过复制粘贴的方式进行复用,维持同一个功能在多个App之间的逻辑一致性也会存在问题;
3 拓展核心能力阶段
开发职责分离,团队成员需要分为两部分,分别对应业务开发和基础架构。业务开发组负责完成日常业务迭代的支撑,以业务交付为目标;基础架构组负责底层基础核心能力建设,以提升效率、性能和核心能力拓展为目标; 项目架构优化,基于1,要在应用层和基础层之间,抽象出核心架构层,并将其连带基础层一起交由基础架构组负责,如图;
4 模块化阶段
5 跨平台开发阶段
三 项目架构拆解
1 基础层
基础UI模块
网络模块
维护团队和社区比较大,遇到问题后能够有足够多的自助解决空间; 底层功能强大,支撑尽可能多的上层应用场景; 拓展能力灵活,支持在框架基础上做能力拓展和AOP处理; Api侧友好,降低上层的理解和使用成本;
// 0. 初始化
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
// 1. 声明服务接口
publicinterfaceGitHubService{
Call<List<Repo>> listRepos( String user);
}
// 2. 通过Retrofit获取服务接口实例
GitHubService service = retrofit.create(GitHubService.class);
// 3. 业务层调用
Call<List<Repo>> repos = service.listRepos("octocat");
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://xxx.xxxxxx.xxx")
.client(new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
public Response intercept(@NonNull Chain chain)throws IOException {
// 添加统一请求头
Request newRequest = chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + token)
.build();
return chain.proceed(newRequest);
}
})
.build()
)
.build();
图片模块
Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(imageView);
Uriuri = Uri.parse("https://raw.githubusercontent.com/facebook/fresco/main/docs/static/logo.png");
SimpleDraweeViewdraweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
draweeView.setImageURI(uri);
Glide.with(fragment)
.load(myUrl)
.into(imageView);
异步模块
主线程做网络请求,会出现NetworkOnMainThreadException异常;
主线程做耗时任务,很可能会出现ANR(全称Application Not Responding,指应用无响应);
子线程做UI操作,会出现CalledFromWrongThreadException异常(这里只做一般性讨论,实际上在满足某些条件下子线程也能更新UI,参《Android 中子线程真的不能更新 UI 吗?》,本文不讨论该情况);
通过主线程Handler的post方法
privatestaticfinal Handler UI_HANDLER = new Handler(Looper.getMainLooper());
privatevoiddoTask()throws Throwable {
Thread.sleep(3000);
UI_HANDLER.post(new Runnable() {
publicvoidrun(){
refreshUI();
}
});
}
通过主线程Handler的sendMessage方法
privatefinal Handler UI_HANDLER = new Handler(Looper.getMainLooper()) {
publicvoidhandleMessage(@NonNull Message msg){
if (msg.what == MSG_REFRESH_UI) {
refreshUI();
}
}
};
privatevoiddoTask()throws Throwable {
Thread.sleep(3000);
UI_HANDLER.sendEmptyMessage(MSG_REFRESH_UI);
}
通过Activity的runOnUiThread方法
publicclassMainActivityextendsActivity{
// ...
privatevoiddoTask()throws Throwable {
Thread.sleep(3000);
runOnUiThread(new Runnable() {
publicvoidrun(){
refreshUI();
}
});
}
}
通过View的post方法
private View view;
privatevoiddoTask()throws Throwable {
Thread.sleep(3000);
view.post(new Runnable() {
publicvoidrun(){
refreshUI();
}
});
}
通过新开线程
privatevoidstartTask(){
new Thread() {
publicvoidrun(){
doTask();
}
}.start();
}
通过ThreadPoolExecutor
privatefinal Executor executor = Executors.newFixedThreadPool(10);
privatevoidstartTask(){
executor.execute(new Runnable() {
publicvoidrun(){
doTask();
}
});
}
通过AsyncTask
private void startTask() {
new AsyncTask<Void, Void, Void>() {
protectedVoid doInBackground(Void... voids) {
doTask();
returnnull;
}
}.execute();
}
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
Future<String> fetchUserOrder() =>
Future.delayed(const Duration(seconds: 2), () =>'Large Latte');
Future<String> createOrderMessage() async {
var order = await fetchUserOrder();
return'Your order is: $order';
}
functionresolveAfter2Seconds(x) {
returnnewPromise(resolve => {
setTimeout(() => { resolve(x); }, 2000);
});
}
asyncfunctionf1() {
var x = await resolveAfter2Seconds(10);
console.log(x); // 10
}
f1();
source
.operator1()
.operator2()
.operator3()
.subscribe(consumer)
2 核心层
动态配置
Android(Native开发)不同于Web能够随时发布上线,Android发布几乎都要走应用平台的审核;
业务上很多时候需要做AB测试或一些配置开关,以满足业务的多样性;
按照namespace进行多模块划分配置,避免全局一个大而全的配置;
每个namespace在初始化和每次改动时都会有个flag标识,以标识当前版本;
客户端每个业务请求都在请求头处统一拉上各flag或他们共同组合的md5等标识,为了在服务端统一拦截时进行flag时效性校验;
服务端时效性检验结果通过统一响应头下发,与业务接口隔离,上层业务方不感知;
客户端收到时效性不一致结果时,再根据具体的namespace进行拉取,而不是每次全量拉取;
全局拦截
弹出Toast
{
"type": "toast",
"content": "您好,欢迎来到XXX",
"gravity": "<这里填写toast要展示的位置, 可选项为(center|top|bottom), 默认值为center>"
}
弹出Dialog 这里值得注意的是,Dialog的Action中嵌套了Toast的逻辑,多种Action的灵活组合能给我们提供丰富的交互能力。
{
"type": "dialog",
"title": "提示",
"message": "确定退出当前页面吗?",
"confirmText": "确定",
"cancelText": "取消",
"confirmAction": {
"type": "toast",
"content": "您点击了确定"
}
}
关闭当前页面
{
"type": "finish"
}
跳转到某个页面
{
"type": "route",
"url": "https://www.xxx.com/goods/detail?id=xxx"
}
执行某个网络请求 同2,这里也做了多Action的嵌套组合。
{
"type": "request",
"url": "https://www.xxx.com/goods/detail",
"method": "post",
"params": {
"id": "xxx"
},
"response": {
"successAction": {
"type": "toast",
"content": "当前商品的价格为${response.data.priceDesc}元"
},
"errorAction": {
"type": "dialog",
"title": "提示",
"message": "查询失败, 即将退出当前页面",
"confirmText": "确定",
"confirmAction": {
"type": "finish"
}
}
}
}
提供根据页面和事件标识来获取服务端下发的Action的能力,这里用到的DynamicConfig即为前面提到的动态配置。
privatestatic Action getClickActionIfExists(String page, String event) {
// 根据当前页面和事件确定动作标识
String actionId = String.format("hook/click/%s/%s", page, event);
// 解析动态配置中, 是否有需要下发的Action
String value = DynamicConfig.getValue(actionId, null);
if (TextUtils.isEmpty(value)) {
returnnull;
}
try {
// 将下发Action解析为结构化数据
returnJSON.parseObject(value, Action.class);
} catch (JSONException ignored) {
// 格式错误时不做处理 (供参考)
}
returnnull;
}
提供包装点击事件的处理逻辑(performAction为对具体Action的解析逻辑,功能比较简单,这里不做展开)
/**
* 包装点击事件的处理逻辑
*
* @param page 当前页面标识
* @param event 当前事件标识
* @param clickListener 点击事件的处理逻辑
*/
publicstatic View.OnClickListener handleClick(String page, String event,
View.OnClickListener clickListener) {
// 这里返回一个OnClickListener对象, 降低上层业务方的理解成本和代码改动难度
returnnew View.OnClickListener() {
publicvoidonClick(View v){
// 取出当前事件的下发配置
Action action = getClickActionIfExists(page, event);
if (action != null) {
// 有配置, 则走配置逻辑
performAction(action);
} elseif (clickListener != null) {
// 无配置, 则走默认处理逻辑
clickListener.onClick(v);
}
}
};
}
// 之前
addGoodsButton.setOnClickListener(new View.OnClickListener() {
publicvoidonClick(View v){
Router.open("https://www.xxx.com/goods/add");
}
});
// 之后
addGoodsButton.setOnClickListener(ActionManager.handleClick(
"goods-manager", "add-goods", new View.OnClickListener() {
publicvoidonClick(View v){
Router.open("https://www.xxx.com/goods/add");
}
}));
{
"hook/click/goods-manager/add-goods": {
"type": "dialog",
"title": "提示",
"message": "由于XX原因,添加商品页面暂不可用",
"confirmText": "确定",
"confirmAction": {
"type": "finish"
}
}
}
本地配置
对本地配置提供默认值支持,未做任何配置时,配置返回默认值。比如,默认环境为线上,如果没有更改配置,就不会读取到日常和预发环境。
简化配置的读写接口,让上层业务方尽可能少感知实现细节。比如,我们不需要让上层感知到本地配置的持久化信息写入的是SharedPreferences还是SQLite,而只需提供一个写入的API即可。
向上层暴露进入本地配置页的API方式,以满足上层选择性进入的空间。比如,上层通过我们暴露出去的API可以选择实际使用App过程中,是通过点击页面的某个操作按钮、还是手机的音量键或者是摇一摇来进入配置页面。
对于App中是否拥有本地配置能力的控制,尽可能放到编译构建级别,保证线上用户不会进入到配置页面。比如,如果线上用户能够进入到预发环境,那很可能会酝酿着一场安全事故即将发生。
版本管理
日志监控
环境隔离,release包禁止log输出;
本地持久化,对于关键重要日志(如某个位置错误引起的Crash)要做本地持久化保存;
日志上报,在用户授权允许的情况下,将暂存用户本地的日志进行上传,并分析具体的操作链路;
埋点统计
客户端埋点一般分为P点(页面级别)、E点(事件级别)和C点(自定义点);
埋点分为收集和上报两个步骤,用户量级较大时要注意对上报埋点进行合并、压缩等优化处理;
埋点逻辑是辅逻辑,产品业务是主逻辑,客户端资源紧张时要做好资源分配的权衡;
热修复
应用出现重大缺陷并严重影响到用户使用时,比如,在某些系统定制化较强的机型(如小米系列)上一旦进入商品详情页就出现应用Crash;
应用出现明显阻塞问题并影响到用户正常交互时,比如,在某些极端场景下,用户无法关闭页面对话框;
应用出现资损、客诉、舆论风波等产品形态问题,比如将价格单位“元”误显示为“分”;
3 应用层
抽象和封装
publicclassGoodsListActivityextendsActivity{
privatefinal List<GoodsModel> dataList = new ArrayList<>();
private Adapter adapter;
protectedvoidonCreate(@Nullable Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_goods_list);
RecyclerView recyclerView = findViewById(R.id.goods_recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new Adapter();
recyclerView.setAdapter(adapter);
// 加载数据
dataList.addAll(...);
adapter.notifyDataSetChanged();
}
privateclassAdapterextendsRecyclerView.Adapter<ViewHolder> {
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position){
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.item_goods, parent, false);
returnnew ViewHolder(view);
}
publicvoidonBindViewHolder(@NonNull ViewHolder holder, int position){
GoodsModel model = dataList.get(position);
holder.title.setText(model.title);
holder.price.setText(String.format("%.2f", model.price / 100f));
}
publicintgetItemCount(){
return dataList.size();
}
}
privatestaticclassViewHolderextendsRecyclerView.ViewHolder{
privatefinal TextView title;
privatefinal TextView price;
publicViewHolder(View itemView){
super(itemView);
title = itemView.findViewById(R.id.item_title);
price = itemView.findViewById(R.id.item_price);
}
}
}
为什么每个列表都要写一个ViewHolder?
为什么每个列表都要写一个Adapter?
为什么Adapter中对itemView的创建和数据绑定要在onCreateViewHolder和onBindViewHolder两个方法中分开进行?
为什么Adapter每次设置数据之后,都要调用对应的notifyXXX方法?
为什么Android上实现一个简单的列表需要大几十行代码量?这其中有多少是必需的,又有多少是可以抽象封装起来的?
publicclassGoodsListActivityextendsActivity{
private RecyclerViewHelper<GoodsModel> recyclerViewHelper;
protectedvoidonCreate(@Nullable Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_goods_list);
RecyclerView recyclerView = findViewById(R.id.goods_recycler_view);
recyclerViewHelper = RecyclerViewHelper.of(recyclerView, R.layout.item_goods,
(holder, model, position, itemCount) -> {
TextView title = holder.getView(R.id.item_title);
TextView price = holder.getView(R.id.item_price);
title.setText(model.title);
price.setText(String.format("%.2f", model.price / 100f));
});
// 加载数据
recyclerViewHelper.addData(...);
}
}
页面埋点,基于前面提到的埋点统计,在用户进入、离开页面等时机,将用户页面的交互情况进行收集上报;
公共UI,页面顶部状态栏和ActionBar、页面常用的下拉刷新能力实现、页面耗时操作时的加载进度条;
权限处理,进入当前页面所需要的权限申请,用户授权后的回调逻辑和拒绝后的异常处理逻辑;
统一拦截,结合前面提到的统一拦截对进入页面后添加支持动态配置交互的定制能力;
模块化
Intent intent = new Intent(this, GoodsActivity.class);
intent.putExtra("goodsId", model.goodsId);
startActivity(intent);
注册 Activity 标识,在 AndroidManifest.xml 中注册 Activity 的地方添加 action 标识
<activityandroid:name=".GoodsActivity">
<intent-filter>
<actionandroid:name="https://www.xxx.com/goods/detail" />
<categoryandroid:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
替换跳转逻辑,代码中根据上一步注册的 Activity 标识进行隐式跳转
Intent intent = new Intent("https://www.xxx.com/goods/detail");
intent.putExtra("goodsId", model.goodsId);
startActivity(intent);
publicclass Router {
/**
* 根据url跳转到目标页面
*
* @param context 当前页面上下文
* @param url 目标页面url
*/
publicstaticvoid open(Context context, String url) {
// 解析为Uri对象
Uri uri = Uri.parse(url);
// 获取不带参数的url
String urlWithoutParam = String.format(
"%s://%s%s", uri.getScheme(), uri.getHost(), uri.getPath());
Intent intent = new Intent(urlWithoutParam);
// 解析url中的参数, 并通过Intent传递至下个页面
for (String paramKey : uri.getQueryParameterNames()) {
String paramValue = uri.getQueryParameter(paramKey);
intent.putExtra(paramKey, paramValue);
}
// 执行跳转操作
context.startActivity(intent);
}
}
Router.open(this, "https://www.xxx.com/goods/detail?goodsId=" + model.goodsId);
抽象统一方法,降低外部编码成本;
统一收口路由逻辑,便于结合前面的「动态配置」和「统一拦截」章节进行线上App路由逻辑的动态更改;
标准化 Android 端页面跳转参数格式,统一使用 String 类型,解除目标页面解析参数时的类型判断歧义;
对跳转页面所需要的数据做了标准化支持,iOS 端再配合同步改造后,页面跳转逻辑将支持完全由业务服务端进行下发;
通知式通信,只需要将事件告知对方,并不关注对方的响应结果。对于该种通信,一般采用如下方式实现
借助 framework 层提供的通过 Intent + BroadcastReceiver (或 LocalBroadcastManager)发送事件; 借助框架 EventBus 发送事件; 基于观察者模式自实现消息转发器来发送事件;
调用式通信,将事件告知对方,同时还关注对方的事件响应结果。对于该种通信,一般采用如下方式实现
定义出 biz-service 模块,将业务接口 interface 文件收口到该模块,再由各接口对应语义的业务模块进行接口的实现,然后再基于某种机制(手动注册或动态扫描)完成实现类的注册; 抽象出 Request => Response 的通信协议,协议层负责完成 先将通过调用方传递的 Request 路由到被调用方的协议实现层; 再将实现层返回结果转化为泛化的 Response对象; 最后将 Response 返回给调用方;
4 跨平台层
活跃度中 目前托管Apache | 活跃度高 Facebook | 活跃度高 Dcloud | 活跃度高 Google | |
四 核心原理总结
1 双缓存
2 线程池
在开发框架中,网络库和图片库获取网络资源需用到线程池;
在项目开发中,读写SQLite和本地磁盘文件等IO操作需要用到线程池;
在类似于AsyncTask这种提供任务调度的API中,其底层也是依赖线程池来完成的;
// 构造线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
keepAliveTime, keepAliveTimeUnit, workQueue, threadFactory, rejectedExecutionHandler);
// 提交子任务
executor.execute(new Runnable() {
publicvoidrun(){
// 这里做子任务操作
}
});
// 核心线程数
int corePoolSize = 5;
// 最大线程数
int maximumPoolSize = 10;
// 闲置线程保活时长
int keepAliveTime = 1;
// 保活时长单位
TimeUnit keepAliveTimeUnit = TimeUnit.MINUTES;
// 阻塞队列
BlockingDeque<Runnable> workQueue = new LinkedBlockingDeque<>(50);
// 线程工厂
ThreadFactory threadFactory = new ThreadFactory() {
public Thread newThread(Runnable r){
returnnew Thread(r);
}
};
// 任务溢出的处理策略
RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.AbortPolicy();
3 反射和注解
前面「热修复」章节,基于ClassLoader 的方案,其内部实现几乎全是通过反射进行 dex 更改的;
前面「网络模块」章节,Retorfit 只需要声明一个接口再添加注解即可使用,其底层也是利用了反射注解和下面要介绍的动态代理技术;
依赖注入框架 dagger 和 androidannotations 利用 Java 的 APT 预编译技术再结合编译时注解做注入代码生成;
如果了解 Java 服务端开发,主流的开发框架 SpringBoot 其内部大量运用了注射和注解的技术;
publicclassDataManager {
private UserHelper userHelper = new UserHelper();
private GoodsHelper goodsHelper = new GoodsHelper();
private OrderHelper orderHelper = new OrderHelper();
}
publicclassDataManager{
private UserHelper userHelper;
private GoodsHelper goodsHelper;
private OrderHelper orderHelper;
publicDataManager(){
// 注入对象实例 (内部通过反射+注解实现)
InjectManager.inject(this);
}
}
publicclassManager {
privatevoiddoSomething(String name) {
// ...
}
}
try {
Class<?> managerType = manager.getClass();
Method doSomethingMethod = managerType.getMethod("doSomething", String.class);
doSomethingMethod.setAccessible(true);
doSomethingMethod.invoke(manager, "<name参数>");
} catch (Exception e) {
e.printStackTrace();
}
4 动态代理
背景
publicclassHttpUtil{
/**
* 执行网络请求
*
* @param relativePath url相对路径
* @param params 请求参数
* @param callback 回调函数
* @param <T> 响应结果类型
*/
publicstatic <T> voidrequest(String relativePath, Map<String, Object> params, Callback<T> callback){
// 实现略..
}
}
publicinterfaceGoodsApi{
/**
* 分页查询商品列表
*
* @param pageNum 页面索引
* @param pageSize 每页数据量
* @param callback 回调函数
*/
voidgetPage(int pageNum, int pageSize, Callback<Page<Goods>> callback);
}
publicclassGoodsApiImplimplementsGoodsApi {
@Override
publicvoidgetPage(int pageNum, int pageSize, Callback<Page<Goods>> callback) {
Map<String, Object> params = new HashMap<>();
params.put("pageNum", pageNum);
params.put("pageSize", pageSize);
HttpUtil.request("goods/page", params, callback);
}
}
GoodsApi goodsApi = new GoodsApiImpl();
goodsApi.getPage(1, 50, new Callback<Page<Goods>>() {
publicvoidonSuccess(Page<Goods> data){
// 成功回调
}
publicvoidonError(Error error){
// 失败回调
}
});
问题
/**
* 查询商品详情
*
* @param id 商品ID
* @param callback 回调函数
*/
voidgetDetail(long id, Callback<Goods> callback);
@Override
publicvoidgetDetail(long id, Callback<Goods> callback) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
HttpUtil.request("goods/detail", params, callback);
}
@Override
publicvoidcreate(Goods goods, Callback<Goods> callback) {
Map<String, Object> params = new HashMap<>();
params.put("goods", goods);
HttpUtil.request("goods/create", params, callback);
}
@Override
publicvoidupdate(Goods goods, Callback<Void> callback) {
Map<String, Object> params = new HashMap<>();
params.put("goods", goods);
HttpUtil.request("goods/update", params, callback);
}
分析
Map<String, Object> params = new HashMap<>();
// 遍历当前方法参数, 执行以下语句
params.put("<参数名>", <参数值>);
HttpUtil.request("<接口路径>", params, callback);
手动写一个 Map;
往 Map 中塞参数键值对;
调用 HttpUtil#request 执行网络请求;
封装
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Path {
/**
* @return 接口路径
*/
Stringvalue();
}
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Param {
/**
* @return 参数名称
*/
Stringvalue();
}
@SuppressWarnings("unchecked")
publicstatic <T> T getApi(Class<T> apiType) {
return (T) Proxy.newProxyInstance(apiType.getClassLoader(), new Class[]{apiType}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 解析接口路径
String path = method.getAnnotation(Path.class).value();
// 解析接口参数
Map<String, Object> params = new HashMap<>();
Parameter[] parameters = method.getParameters();
// 注: 此处多偏移一位, 为了跳过最后一项callback参数
for (int i = 0; i < method.getParameterCount() - 1; i++) {
Parameter parameter = parameters[i];
Param param = parameter.getAnnotation(Param.class);
params.put(param.value(), args[i]);
}
// 取最后一项参数为回调函数
Callback<?> callback = (Callback<?>) args[args.length - 1];
// 执行网络请求
HttpUtil.request(path, params, callback);
returnnull;
}
});
}
效果
publicinterfaceGoodsApi{
void getPage( int pageNum, int pageSize, Callback<Page<Goods>> callback);
void getDetail( long id, Callback<Goods> callback);
void create( Goods goods, Callback<Goods> callback);
void update(Void> callback); Goods goods, Callback<
}
// 之前
GoodsApi goodsApi = new GoodsApiImpl();
// 现在
GoodsApi goodsApi = ApiProxy.getApi(GoodsApi.class);
五 通用设计方案
通信设计
直接依赖关系
publicclassMainActivityextendsActivity{
protectedvoidonCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
// 略...
// A调用B
button.setText("");
button.setOnClickListener(new View.OnClickListener() {
publicvoidonClick(View v){
// B回调A
}
});
}
}
间接依赖关系
publicclassGoodsCardViewextendsFrameLayout{
privatefinal Button button;
private OnFollowListener followListener;
publicGoodsCardView(Context context, AttributeSet attrs){
super(context, attrs);
// 略...
button.setOnClickListener(new View.OnClickListener() {
publicvoidonClick(View v){
if (followListener != null) {
// C回调B
followListener.onFollowClick();
}
}
});
}
publicvoidsetFollowText(String followText){
// C调用B
button.setText(followText);
}
publicvoidsetOnFollowClickListener(OnFollowListener followListener){
this.followListener = followListener;
}
}
publicclassMainActivityextendsActivity{
private GoodsCardView goodsCard;
protectedvoidonCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
// 略...
// A调用C
goodsCard.setFollowText("点击商品即可关注");
goodsCard.setOnFollowClickListener(new OnFollowListener() {
publicvoidonFollowClick(){
// C回调A
}
});
}
}
组合关系
publicclassMainActivityextendsActivity{
private RecyclerView recyclerView;
private ImageView topIcon;
protectedvoidonCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
// 略...
topIcon.setOnClickListener(new View.OnClickListener() {
publicvoidonClick(View v){
// B回调C
onTopIconClick();
}
});
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
publicvoidonScrollStateChanged(RecyclerView recyclerView, int newState){
// A回调C
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
onFirstItemVisibleChanged(layoutManager.findFirstVisibleItemPosition() == 0);
}
}
});
}
privatevoidonFirstItemVisibleChanged(boolean visible){
// C调用B
topIcon.setVisibility(visible ? View.GONE : View.VISIBLE);
}
privatevoidonTopIconClick(){
// C调用A
recyclerView.scrollToPosition(0);
// C调用B
topIcon.setVisibility(View.GONE);
}
}
深依赖/组合关系
publicclassEventManagerextendsObservable<EventManager.OnEventListener> {
publicinterfaceOnEventListener{
voidonEvent(String action, Object... args);
}
publicvoiddispatch(String action, Object... args){
synchronized (mObservers) {
for (OnEventListener observer : mObservers) {
observer.onEvent(action, args);
}
}
}
}
publicclassAComponent{
publicstaticfinal String ACTION_SOMETHING = "a_do_something";
privatefinal EventManager eventManager;
publicAComponent(EventManager eventManager){
this.eventManager = eventManager;
}
publicvoidsendMessage(){
// A调用X
eventManager.dispatch(ACTION_SOMETHING);
}
}
publicclassBComponent{
privatefinal EventManager eventManager;
publicBComponent(EventManager eventManager){
this.eventManager = eventManager;
eventManager.registerObserver(new EventManager.OnEventListener() {
publicvoidonEvent(String action, Object... args){
if (AComponent.ACTION_SOMETHING.equals(action)) {
// X分发B
}
}
});
}
}
无关系
可拓展回调函数设计
背景
publicinterfaceCallback {
voidonCall1();
}
publicclassSDKManager {
private Callback callback;
publicvoidsetCallback(Callback callback) {
this.callback = callback;
}
privatevoiddoSomething1() {
// 略...
if (callback != null) {
callback.onCall1();
}
}
}
SDKManager sdkManager = new SDKManager();
sdkManager.setCallback(new Callback() {
publicvoidonCall1(){
}
});
问题
publicinterfaceCallback {
voidonCall1();
voidonCall2();
}
sdkManager.setCallback(new Callback() {
publicvoidonCall1(){
}
});
publicinterfaceCallback2 {
voidonCall2();
}
publicclassSDKManager {
// 略..
private Callback2 callback2;
publicvoidsetCallback2(Callback2 callback2) {
this.callback2 = callback2;
}
privatevoiddoSomething2() {
// 略...
if (callback2 != null) {
callback2.onCall2();
}
}
}
sdkManager.setCallback2(new Callback2() {
publicvoidonCall2(){
}
});
对外优化
publicinterfaceCallback {
}
publicinterfaceCallback1extendsCallback{
voidonCall1();
}
publicinterfaceCallback2extendsCallback{
voidonCall2();
}
publicclassSDKManager{
private Callback callback;
publicvoidsetCallback(Callback callback){
this.callback = callback;
}
privatevoiddoSomething1(){
// 略...
if ((callback instanceof Callback1)) {
((Callback1) callback).onCall1();
}
}
privatevoiddoSomething2(){
// 略...
if ((callback instanceof Callback2)) {
((Callback2) callback).onCall2();
}
}
}
publicclassSimpleCallbackimplementsCallback1, Callback2{
publicvoidonCall1(){
}
publicvoidonCall2(){
}
}
// 单接口方式设置回调
sdkManager.setCallback(new Callback1() {
publicvoidonCall1(){
// ..
}
});
// 组合接口方式设置回调
interfaceCombineCallbackextendsCallback1, Callback2{
}
sdkManager.setCallback(new CombineCallback() {
publicvoidonCall1(){
// ..
}
publicvoidonCall2(){
// ...
}
});
// 空实现类方式设置回调
sdkManager.setCallback(new SimpleCallback() {
publicvoidonCall1(){
// ..
}
publicvoidonCall2(){
//..
}
});
publicinterfaceCallback3extendsCallback{
voidonCall3();
}
privatevoiddoSomething3(){
// 略...
if ((callback instanceof Callback3)) {
((Callback3) callback).onCall3();
}
}
对内优化
privatevoiddoSomething1(){
// 略...
if ((callback instanceof Callback1)) {
((Callback1) callback).onCall1();
}
}
publicclassCallbackProxyimplementsCallback1, Callback2, Callback3{
private Callback callback;
publicvoidsetCallback(Callback callback){
this.callback = callback;
}
publicvoidonCall1(){
if (callback instanceof Callback1) {
((Callback1) callback).onCall1();
}
}
publicvoidonCall2(){
if (callback instanceof Callback2) {
((Callback2) callback).onCall2();
}
}
publicvoidonCall3(){
if (callback instanceof Callback3) {
((Callback3) callback).onCall3();
}
}
}
publicclassSDKManager{
privatefinal CallbackProxy callbackProxy = new CallbackProxy();
publicvoidsetCallback(Callback callback){
callbackProxy.setCallback(callback);
}
privatevoiddoSomething1(){
// 略...
callbackProxy.onCall1();
}
privatevoiddoSomething2(){
// 略...
callbackProxy.onCall2();
}
privatevoiddoSomething3(){
// 略...
callbackProxy.onCall3();
}
}
《Android 中子线程真的不能更新 UI 吗?》:https://juejin.cn/post/6844904131136618510
《移动跨平台开发框架解析与选型》:https://segmentfault.com/a/1190000039122907
大数据Impala教程
最新评论
推荐文章
作者最新文章
你可能感兴趣的文章
Copyright Disclaimer: The copyright of contents (including texts, images, videos and audios) posted above belong to the User who shared or the third-party website which the User shared from. If you found your copyright have been infringed, please send a DMCA takedown notice to [email protected]. For more detail of the source, please click on the button "Read Original Post" below. For other communications, please send to [email protected].
版权声明:以上内容为用户推荐收藏至CareerEngine平台,其内容(含文字、图片、视频、音频等)及知识版权均属用户或用户转发自的第三方网站,如涉嫌侵权,请通知[email protected]进行信息删除。如需查看信息来源,请点击“查看原文”。如需洽谈其它事宜,请联系[email protected]。
版权声明:以上内容为用户推荐收藏至CareerEngine平台,其内容(含文字、图片、视频、音频等)及知识版权均属用户或用户转发自的第三方网站,如涉嫌侵权,请通知[email protected]进行信息删除。如需查看信息来源,请点击“查看原文”。如需洽谈其它事宜,请联系[email protected]。