5分钟搞定!宅家36天咸鱼翻身入职腾讯,好文推荐

前言

在做android项目开发时,大家都知道如果程序出错了,会弹出来一个强制退出的弹 出框,这个本身没什么问题,但是这个UI实在是太丑了,别说用户接受不了,就连 我们自己本身可能都接受不了。虽然我们在发布程序时总会经过仔细的测试,但是 难免会碰到预料不到的错误。

实现方案

直接依赖

这种方式实现简单,但是耦合太严重,不方便维护与开发,当工程逐渐增大模块逐渐增多,依赖关系会非常复杂,不推荐这种方式。

事件或广播通信

EventBus: 我们非常熟悉的事件总线型的通信框架,非常灵活,采用注解方式实现,但是难以追溯事件。广播: 安卓的四大组件之一,在一个模块中发送广播设置数据,在另一个模块中注册广播接收数据,使用广播进行数据传递方式广播相对于其他的方式而言消耗资源较多。

总结: BroadcastReceiver、EventBus,非常灵活,模块之间没有任何的耦合,但是代码的可读性差,难以追溯事件,不是很推荐。

路由通信

模块与模块之间不存在依赖关系,而是各自运作,简单的来说就是映射关系的路由通信,也是目前比较主流的一种方案,比较常用的开源框架是阿里的ARouter。

ARouter典型应用

从外部URL映射到内部页面,以及参数传递与解析跨模块页面跳转,模块间解耦拦截跳转过程处理登陆、埋点等逻辑跨模块API调用,通过控制反转来做组件解耦。

面向接口通信

以上几种方式只是简单的介绍,下面就具体说下通过接口解耦通信的方式,首先先看几个问题。

什么是面向接口编程?

接口大家都很熟悉,这里所说的面向接口编程,并不只是所谓的 java 中的 interface,而是指超类型,可以是接口也可以是抽象类。

面向接口比面向对象编程是更先进一步编程思想,而是附属于面向对象编程的体系,属于其中一部分,它是面向对象编程体系中的思想精髓之一。面向接口编程它的核心思想是将抽象与实现分离,从组件的级别来设计代码,达到高内聚低耦合的目的。面向接口编程方法是,先定义底层接口模块,也就是 通信的协议与功能约定 ,是提供方实现对应的功能与能力。在架构中层次分明,不需要关注具体实现,开发中可以通过接口快速制定协议,与提供能力api,对于上层通过接口显露能力,对于下层只需要依赖接口层相当于依赖api。

面向接口编程的好处?

灵活性高没有依赖具体的实体,实现层可以任意的更改与切换。在模块化中可以相互依赖service(接口层)或依赖多个。‍

‍在模块化中的使用下面对于接口(interface)或api层统称为service,其含义为服务提供者。

对于,每一个 module 都一个独立的工程结构,每个 module 都有自己的 Service ,来统一暴露当前 module 所拥有能力与向外提供的服务。


对于 module 是在同一个工程里的项目结构,service 可以放到统一的一个 Module 下,我们统称为 Mediator,这样做的目的是为了减少 Module 创建与维护。假设你的工程有20个业务 Module 如果都同时增加一个 service 层就会造成 Module 数量翻一倍。由于这里存入的都一些接口类,也是每个业务 Module 向外提供的服务其体量不会太大,这里并只是一个建议并没有标准的做法。

当然也有更复杂的设计,一个 Module 又分不同的 service 实现如图,这里不在展开细说。

实际工程中使用与设计

在实际项目中有很多项目都同时开发两版本Pad与Phone,有的是两独立的工程,有的是在同一个工程内用 flavor 切换不同的工程,下面我就以通过 flavor 切换的工程结构举例。先看下工程的包的结构图:

可以看到 module 结构是分为三个部分,common, pad, phone, 如果每个service 都独立将增加3倍的 Module 数量。

使用一个 Mediator Module 统一管理这这些 service 就很好控制了 module 数量。

Service 创建

在 module_mediator 业务 module 下 common,pad、pone 下分别创建ICommonService, IService(pad), IService(phone)。ICommonService:公共服务。IService(pad):pad服务并继承CommonService。IService(phone):phone服务并继承CommonService。

注:这里为什么不用,PadService与PhoneService,是因为pad与phone版本同时只会存在一个,使用方只需要关心你提供的Service不用在区分版本,而且这里是一个继承关系也可以获取到共用的部分。

Service 实现

依赖 Mediator :

在业务 common\pad\phone module 下分别实现,ICommonService, Service(pad), IService(phone) ,在 common module 创建 CommonServiceImpl 实现 ICommonService,在 pad、phone module 分别创建 ServiceImpl 对应实现 IService 并继承与 CommonServiceImpl。

Service 注册

注册的方式有一般是通过代码用去注册,或通过注解进行注册。可以在 Application 注册也可以在业务 Module 下自己注册,如果使用注解则可以自动注册,具体要看项目怎样实现。例:

解释下 MediatorServiceFacator,它只是一个服务工厂也是一个接口类,作用是负责管理各业务方的 Service 主要功能是注册与获取 Service。上面的代码就是往里注册了一个会员的 Service。

可以看出这个函数只有两个参数,一个是接口class一个是实现类class,第一个参数cls:它会作为 key 来使用,第二个参数implClass:它会作为 value 来使用。

Service 使用

通过 MediatorServiceFacator 懒加载获取service对象,如果业务方没有注册则获取一个空的对象。

注册有 service 没有使用时是不会创建的,如果使用过则会缓存下来,下次调用则直接返回。(第一次是通过反射创建)例:

  1. 在 mediator 模块下会员 CommonService 中 定义了一个模糊查询会员的方法。

  1. 在会员模块下 common 中实现了该功能。

  1. 在会员模块下 pad 中继承了这个实现。

  1. 在其他模块 pad 下使用这个功能。

可以看到获取 Service 只要传对应接口就即可,对于使用方是不用关心实现方,在开发过程中只要先定义好接口,合作的同学就可以进入正常开发了。细心的同学可以看出,返回的数据类型也是一个接口类,为什么不直接返回一个普通 java 类呢?主要原因是通过接口方法达到双方 api 约定,例如 getName() :String 方法是通过方法名返回值达到约定效果,这样不依赖具体实现。

从上面的例子可以看出主要分为三个部分:1、定义接口。2、提供方实现接口。3、使用方都通过服务工厂获取服务使用。对于使用者来是很简单的,不需要关心实现,通过接口可以直接获取到实现,并且获取到结果可以直接使用,不需要做序列化处理。

有了路由通信我们为什么还使用面向接口编程?

路由模式虽然很好的解决了耦合的问题,但他的方法调用都是静态的,对于传参与返回值只能是基本类型,如果是对象需要做序列化与反序列化处理,对性能有一定影响。类似在调后台接口一样,同时降低了代码的可读性, 对于 app 而言所有 Module 都是在同一个应用下,没有必要做这些序列化操作。

对于复杂业务不好处理,例如一个业务需要多次通信,路由模式则不好处理,而通过接口通信则可以容易解决。例如:一个读卡的操作,业务方需要对它有开启、关闭、暂停等多个状态的操作。通过接口则可以直接返回一个读卡的 service 控制器, 这样可以直接进行相应的控制操作。

从上面代码中可以看出,上层回调结果的同时并回调了一个控制接口,这样就提供使用方一个反向操作的能力。

写在最后

很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从哪里入手去学习,对此我整理了一些资料,需要的可以免费分享给大家

我的【Github】会分享一些关于Android进阶方面的知识,也会分享一下最新的面试题~

如果你熟练掌握GitHub中列出的知识点,相信将会大大增加你通过前两轮技术面试的几率!这些内容都供大家参考,互相学习。

①「Android面试真题解析大全」PDF完整高清版+②「Android面试知识体系」学习思维导图压缩包——————可以在我的【Github】阅读下载,最后觉得有帮助、有需要的朋友可以点个赞

%AB%98%E8%96%AA%EF%BC%81.md)阅读下载**,最后觉得有帮助、有需要的朋友可以点个赞

[外链图片转存中…(img-zYOshQZq-1620812977702)]

[外链图片转存中…(img-qZ7cXGIX-1620812977703)]

Android-Plugin-Framework 此项目是Android插件框架完整源码以及实例。用来开发Android插件APK,并通过动态加载的方式在宿主程序中运行。 若插件APK是完全独立的APK,那么插件apk也可独立安装运行。 若插件APK不是完全独立的apk,比如和插件宿主程序共用一些依赖库,那么插件apk只能在宿主程序中运行。不可独立运行。 因为此时插件apk的代码是不完整的。 目录结构说明: PluginCore工程是插件库核心工程。用于提供对插件功能的支持。 PluginMain是用来测试的插件宿主程序Demo工程。 PluginShareLib是用来测试的插件宿主程序的依赖库Demo工程 PluginTest是用来测试的插件Demo工程。此工程下有用来编译插件的ant脚本。 宿主程序工程可以通过ant编译或者导入eclipse后直接点击Run菜单进行安装。 插件Demo工程需要通过插件ant脚本编译。编译命令为 “ant clean debug” 原因是Demo中引用了宿主程序的依赖库。需要在编译时对共享库进行排除。 插件编译出来以后,可以将插件复制到sdcard,然后在宿主程序中调用PluginLoader.installPlugin("插件apk绝对路径")进行安装 还有一种简易的安装方式,是使用编译命令为 “ant clean debug install” 直接将插件apk安装到系统中,PluginMain工程会监听系统的应用安装广播,监听到插件apk安装广播后, 再自动调用PluginLoader.installPlugin("/data/app/插件apk文件.apk")进行插件安装。免去复制到sdcard的过程。 (虽然没有用过apkplug、以及另外一个插件框架作者singwhatiwanna写的DL框架,但是看过他们的一些介绍文档,感觉自己的这份实现应该是更简单易用更完善。。。哈哈,是不是有王婆卖瓜的嫌疑。) 已支持的功能: 1、插件apk无需安装,由宿主程序动态加载运行。 2、插件形式支持fragment和activity代理。 这两种形式是插件开发中的两种主要形式。 3、插件支持activity非代理模式,真正实现Activity无需注册,既不用反射,也不用代理,实现Activity完整生命周期。 4、插件库apk可无任何特殊代码。如插件中的fragment,activity等无需继承任何特定基类或接口。完全和普通app代码没有区别. 5、支持插件共用宿主程序依赖库提供的自定义控件 6、支持插件中使用自定义控件 7、支持插件资源和宿主资源混合使用。意即支持如下场景: 插件程序和宿主程序共用依赖库时插件中的布局文件中使用了宿主程序中的自定义控件,而宿主程序中的自定义控件中又使用 了宿主程序中的布局文件。 插件代码中无需做任何特殊处理,即可支持这种布局文件。 8、插件中的各种资源、布局、R、以及宿主程序中的各种资源、布局、R等可随意使用、也无任何特殊代码。 10、支持插件使用宿主程序主题和系统主题 11、解决资源id冲突问题。尝试做过插件开发的同学应该都遇到,插件资源id和宿主程序资源id可能相同,导致获取的资源不是想要的资源。 此问题其实在android提供的编译器aapt中早已提供了支持。 12、需要关注PluginTest工程的ant.properties文件和project.properties文件以及custom_rules.xml文件,插件使用宿主程序共享库,以及共享库R引用,和编译时排除的功能,都在这3个配置文件中体现 暂不支持的功能: 1、暂不支持使用插件中自定义主题, 2、支持在插件中通过R文件使用宿主程序中的资源,暂不支持插件资源文件中直接使用宿主程序中的资源。但是支持间接使用。 例如在上述“已支持的功能”6中描述的,实际就是间接使用。 后续需要解决的问题: 1、使支持插件自定义主题 2、使插件中对activity的支持更稳定。 由于此框架没有实际的项目应用,目前对activity的提供标准API的测试还不够完全,可能在其他开发场景中,activity的部分标准API可能会出现问题。毕竟这里使用了很多反射,会涉及到多机型多系统版本的兼容问题。后续还需要持续测试和完善 上述第2点问题已解决、请看已支持的功能第3条。 3、使支持插件资源文件中直接引用依赖库中的资源。目测可能需要重写android自带的aapt程序。 实现原理简介: 1、插件apk的class 通过构造插件apk的Dexclassloader来加载插件apk中的类。DexClassLoader的parent设置为宿主程序的classloader,即可将主程序和插件程序的class贯通 2、插件apk的资源 通过构造插件apk的AssetManager和Resouce类即可。 本项目最关键一点功能、也是和网上其他插件项目不同的地方之一,在于 通过addAssetsPath方法添加资源的时候,同时添加了插件程序的资源文件和宿主程序的资源。这样就 可以做到插件资源合并。很多资源文件都迎刃而解。 3、插件apk中的资源id 完成上述第二点以后,还有需要解决的难题,是宿主程序资源id和插件程序id重复的问题。 这个问题解决办法也很简单 我们知道,资源id是在编译时生成的,其生成的规则是0xPPTTNNNN PP段,是用来标记apk的,默认情况下系统资源PP是01,应用程序的PP是07 TT段,是用来标记资源类型的,比如图标、布局等,相同的类型TT值相同,但是同一个TT值不代表同一种资源,例如这次编译的时候可能使用03作为layout的TT,那下次编译的时候可能会使用06作为TT的值,具体使用那个值,实际上和当前APP使用的资源类型的个数是相关联的。 NNNN则是某种资源类型的资源id,默认从1开始,依次累加。 那么我们要解决资源id问题,就可从TT的值开始入手,只要将每次编译时的TT值固定,即可是资源id达到分组的效果,从而避免重复。 例如将宿主程序的layout资源的TT固定为03,将插件程序资源的layout的TT值固定为23,即可解决资源id重复的问题了。 固定资源idTT值的版本也非常简单,提供一份public.xml,在public.xml中指定什么资源类型以什么TT值开头即可。 具体public.xml如何编写,可参考PluginMain/res/values/public.xml 以及 PluginTest/res/values/public.xml俩个文件,它们是分别用来固定宿主程序和插件程序资源id的范围的。 4、插件apk的Context 构造一个Context即可,具体的Context实现请参考PluginCore/src/com/plugin/core/PluginContextTheme.java 关键是要重写几个获取资源、主题的方法,以及重写getClassLoader方法 5、插件中的LayoutInfalter 通过第4步构造出来的Context获取LayoutInfater即可 6、如何实现插件代码不依赖任何特殊代码,如继承特定的基类、接口等。 要做到这一点,最主要的是实现上述第4步,构造出插件的Context后,所有问题都可以得到解决。 7、插件中Activity无需注册,拥有完整生命周期是如何实现的。 众所周知、Activity是系统组件,必须在manifest中注册才能被系统唤起并拥有完整生命周期,通过Activity代理和放射的方式实现的 实际是伪生命周期。并非完整生命周期。那么如果才能实现activity免注册,而且拥有完整的生命周期呢,这要从activity的启动流程中 入手。 App安装时,系统会扫描app的Manifest并缓存到一个xml中,activity启动时,系统会现在查找缓存的xml,如果查到了,再通过classLoad去load这个class,并构造一个activity实例。那么我们只需要将classload加载这个class的时候做一个简单的映射,让系统以为加载的是A class,而实际上加载的是B class,达到挂羊头买狗肉的效果,即可将预注册的Aclass替换为未注册的activity,从而实现插件中的Activity 完全被系统接管,而拥有完整生命周期。 在PluginMain和PluginTest已经添加了这种实现方式的测试实例。 8、通过activity代理方式实现加载插件中的activity是如何实现的 要实现这一点,同样是基于上述第4点,构造出插件的Context后,通过attachBaseContext的方式,替换代理Activiyt的context即可。 另外还需要在获得插件Activity对象后,通过反射给Activity的attach()方法中attach的成员变量赋值。 这样可解决另外一个插件框架作者singwhatiwanna实现的代码中所谓this和that的问题。也是可以使插件Activity不需要继承任何特定基类或者接口的原因。 9、activity代理实现后,其他组件,如service等,如法炮制即可。 10、插件编译问题。 如果插件和宿主共享依赖库,那边编译插件的时候不可将共享库编译到插件当中,包括共享库的代码以及R文件,但是需要在编译时添加到classpath中,且插件中如果要使用共享依赖库中的资源,需要使用共享库的R文件来进行引用。这几点在PluginTest示例工程中有体现。 11、插件开发模式 插件UI可通过fragment或者activity来实现 如果是activity实现的插件,则最终会在PluginProxyActivity中运行 如果是fragment实现的插件,又分为两种 1种是fragment运行在任意支持fragment的activity中,这种方式,在开发fragment的时候,fragmeng中凡是要使用context的地方,都需要使用通过PluginLoader.getPluginContext()获取的context,那么这种fragment对其运行容器没有特殊要求 还有1种是,fragment运行在PluginCore提供的PluginSpecDisplayer中,这种方式,由于其运行容器PluginSpecDisplayer的Context已经被PluginLoader.getPluginContext获取的context替换,因此这种fragment的代码和普通非插件开发时开发的fragment的代码没有任何区别。 需要注意的问题 项目插件开发后,特别需要注意的是宿主程序混淆问题。宿主程序混淆后,可能会导致插件程序运行时出现classnotfound 标签:Android
CJFrameForAndroid 是一个实现android插件化开发的框架。使用CJFrameForAndroid,apk动态加载不再是难题,更重要的是可以轻松实现插件与APP项目之间的解耦。 原理描述 CJFrameForAndroid的实现原理是通过类加载器,动态加载存在于SD卡上的apk包中的Activity。通过使用一个托管所,插件Activity全部事务(包括声明周期与交互事件)将交由托管所来处理,间接实现插件的运行。一句话描述:CJFrameForAndroid中的托管所,复制了插件中的Activity,来替代插件中的Activity与用户交互。 框架使用 ●使用 CJFrameForAndroid 插件开发框架需要在你项目的AndroidManifest.xml文件中加入托管所的声明。<activity android:name="org.kymjs.aframe.plugin.CJProxy" /> ●让插件应用中的Activity继承CJActivity,并且一切使用this调用的方法都使用that替代。例如this.setContentView();需要改为that.setContentView();●插件中涉及到的Android权限,须在APP项目清单中具有声明。●插件Activity跳转时,推荐使用CJActivityUtils类来辅助跳转。若一定要startActivity或 startActivityForResult,在跳转过程中的Intent不能自己new,必须使用 CJActivityUtils.getPluginIntent();●在插件和APP两个工程中不能引用相同的jar包。解决办法是:在插件工程的项目中添加一个/cjlibs的文件夹,将需要调用的jar包放到这个文件夹中,并在插件项目目录下的.classpath中加入如下语句,系统会自动处理相关细节<classpathentry kind="lib" path="cjlibs"/>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值