写点什么

“终于懂了” 系列,android 面试简历模板

用户头像
Android架构
关注
发布于: 刚刚

...if (isModule.toBoolean()) {// 独立调试时添加 applicationId ,集成调试时移除 applicationId "com.hfy.componentlearning.cart"}...}


sourceSets {main {// 独立调试与集成调试时使用不同的 AndroidManifest.xml 文件 if (isModule.toBoolean()) {manifest.srcFile 'src/main/moduleManifest/AndroidManifest.xml'} else {manifest.srcFile 'src/main/AndroidManifest.xml'}}}...}


可见也是使用 isModule 分别设置 applicationId、AndroidManifest。其中独立调试的 AndroidManifest 是新建于目录 moduleManifest,使用 manifest.srcFile 即可指定两种调试模式的 AndroidManifest 文件路径。



moduleManifest 中新建的 manifest 文件 指定了 Application、启动 activity:


//moduleManifest/AndroidManifest.xml<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.hfy.module_cart" ><application android:name=".CartApplication"android:allowBackup="true"android:label="Cart"android:theme="@style/Theme.AppCompat"><activity android:name=".CartActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application></manifest>


原本自动生成的 manifest,未指定 Application、启动 activity:


<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.hfy.module_cart"><application><activity android:name=".CartActivity"></activity></application></manifest>


独立调试、集成调试 ,分别使用“assembleDebug”构建结果如下:


3.2 多工程方案

3.2.1 方案概述

多工程方案,业务组件以 library module 形式存在于独立的工程。独立工程 自然就可以独立调试了,不再需要进行上面那些配置了。


例如,购物车组件 就是 新建的工程 Cart 的 module_cart 模块,业务代码就写在 module_cart 中即可。app 模块是依赖 module_cart。app 模块只是一个组件的入口,或者是一些 demo 测试代码。



那么当所有业务组件都拆分成独立组件时,原本的工程就变成一个只有 app 模块的壳工程了,壳工程就是用来集成所有业务组件的。

3.2.1 maven 引用组件

那么如何进行集成调试呢?使用 maven 引用组件:1、发布组件的 arr 包 到公司的 maven 仓库,2、然后在壳工程中就使用 implemention 依赖就可以了,和使用第三方库一毛一样。另外 arr 包 分为 快照版本(SNAPSHOT) 和 正(Realease)式版本,快照版本是开发阶段调试使用,正式版本是正式发版使用。具体如下:


首先,在 module_cart 模块中新建 maven_push.gradle 文件,和 build.gradle 同级目录


apply plugin: 'maven'


configurations {deployerJars}


repositories {mavenCentral()}


//上传到 Maven 仓库的 taskuploadArchives {repositories {mavenDeployer {pom.version = '1.0.0' // 版本号 pom.artifactId = 'cart' // 项目名称(通常为类库模块名称,也可以任意)pom.groupId = 'com.hfy.cart' // 唯一标识(通常为模块包名,也可以任意)


//指定快照版本 maven 仓库 url, todo 请改为自己的 maven 服务器地址、账号密码 snapshotRepository(url: 'http://xxx/maven-snapshots/') {authentication(userName: '', password: '')}//指定正式版本 maven 仓库 url, todo 请改为自己的 maven 服务器地址、账号密码 repository(url: 'http://xxx/maven-releases/') {authentication(userName: '', password: '')}}}}


// type 显示指定任务类型或任务, 这里指定要执行 Javadoc 这个 task,这个 task 在 gradle 中已经定义 task androidJavadocs(type: Javadoc) {// 设置源码所在的位置 source = android.sourceSets.main.java.sourceFiles}


// 生成 javadoc.jartask androidJavadocsJar(type: Jar) {// 指定文档名称 classifier = 'javadoc'from androidJavadocs.destinationDir}


// 打包 main 目录下代码和资源的 task,生成 sources.jartask androidSourcesJar(type: Jar) {classifier = 'sources'from android.sourceSets.main.java.sourceFiles}


//配置需要上传到 maven 仓库的文件 artifacts {archives androidSourcesJararchives androidJavadocsJar}


maven_push.gradle 主要就是发布组件 ARR 的配置:ARR 的版本号、名称、maven 仓地址账号等。


然后,再 build.gradle 中引用:


//build.gradleapply from: 'maven_push.gradle'


接着,点击 Sync 后,点击 Gradle 任务 uploadArchives,即可打包并发布 arr 到 maven 仓。



最后,壳工程要引用组件 ARR,需要先在壳工程的根目录下 build.gradle 中添加 maven 仓库地址:


allprojects {repositories {google()jcenter()//私有服务器仓库地址 maven {url 'http://xxx'}}}


接着在 app 的 build.gradle 中添加依赖即可:


dependencies {...implementation 'com.hfy.cart:cart:1.0.0'//以及其他业务组件}


可见,多工程方案 和我们平时使用第三方库是一样的,只是我们把组件 ARR 发布到公司的私有 maven 仓而已。


实际上,我个人比较建议 使用多工程方案的。


  • 单工程方案没法做到代码权限管控,也不能做到开发人员职责划分明确,每个开发人员都可以对任意的组件进行修改,显然还是会造成混乱。

  • 多工程把每个组件都分割成单独的工程,代码权限可以明确管控。集成测试时,通过 maven 引用来集成即可。并且业务组件和业务基础组件也可以 和 基础组件一样,可以给公司其他项目复用。


注意,我在 Demo 里 使用的是多工程方案,并且是 把ARR发到JitPack仓,这样是为了演示方便,和发到公司私有 maven 仓是一个意思。 1、需要根目录下 build.gradle 中添加 JitPack 仓地址:maven { url 'jitpack.io' } ; 2、JitPack 是自定义的 Maven 仓库,不过它的流程极度简化,只需要输入 Github 项目地址就可发布项目。

四、页面跳转

4.1 方案—ARouter

前面说到,组件化的核心就是解耦,所以组件间是不能有依赖的,那么如何实现组件间的页面跳转呢?


例如 在首页模块 点击 购物车按钮 需要跳转到 购物车模块的购物车页面,两个模块之间没有依赖,也就说不能直接使用 显示启动 来打开购物车 Activity,那么隐式启动呢? 隐式启动是可以实现跳转的,但是隐式 Intent 需要通过 AndroidManifest 配置和管理,协作开发显得比较麻烦。这里我们采用业界通用的方式—路由


比较著名的路由框架 有阿里的ARouter、美团的WMRouter,它们原理基本是一致的。


这里我们采用使用更广泛的 ARouter:“一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦”。

4.2 ARouter 实现路由跳转

前面提到,所有的业务组件都依赖了 Common 组件,所以我们在 Common 组件中使用关键字**“api”**添加的依赖,业务组件都能访问。 我们要使用 ARouter 进行界面跳转,需要 Common 组件添加 Arouter 的依赖(另外,其它组件共同依赖的库也要都放到 Common 中统一依赖)。

4.2.1 引入依赖

因为 ARouter 比较特殊,“arouter-compiler ” 的 annotationProcessor 依赖 需要所有使用到 ARouter 的组件中都单独添加,不然无法在 apt 中生成索引文件,就无法跳转成功。并且在每个使用到 ARouter 的组件的 build.gradle 文件中,其 android{} 中的 javaCompileOptions 中也需要添加特定配置。然后壳工程需要依赖业务组件。如下所示:


//common 组件的 build.gradledependencies {...api 'com.alibaba:arouter-api:1.4.0'annotationProcessor 'com.alibaba:arouter-compiler:1.2.1'//业务组件、业务基础组件 共同依赖的库(网络库、图片库等)都写在这里~}


//业务组件的 build.gradleandroid {...defaultConfig {...javaCompileOptions {annotationProcessorOptions {arguments = [AROUTER_MODULE_NAME: project.getName()]}}}...}dependencies {...annotationProcessor 'com.alibaba:arouter-compiler:1.2.1'implementation 'com.github.hufeiyang:Common:1.0.0'//业务组件依赖 common 组件}


//壳工程 app module 的 build.gradledependencies {...//这里没有使用私有 maven 仓,而是发到 JitPack 仓,一样的意思~// implementation 'com.hfy.cart:cart:1.0.0'implementation 'com.github.hufeiyang:Cart:1.0.1' //依赖购物车组件 implementation 'com.github.hufeiyang:HomePage:1.0.2' //依赖首页组件


//壳工程内 也需要依赖 Common 组件,因为需要初始化 ARouterimplementation 'com.github.hufeiyang:Common:1.0.0'}

4.2.2 初始化

依赖完了,先要对 ARouter 初始化,需要在 Application 内完成:


public class MyApplication extends Application {


@Overridepublic void onCreate() {super.onCreate();


// 这两行必须写在 init 之前,否则这些配置在 init 过程中将无效 if (BuildConfig.DEBUG) {// 打印日志 ARouter.openLog();// 开启调试模式(如果在 InstantRun 模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)ARouter.openDebug();}// 尽可能早,推荐在 Application 中初始化 ARouter.init(this);}}

4.2.3 路由跳转

好了,准备工作都完成了。并且知道 首页组件是没有依赖购物车组件的,下面就来实现前面提到的 首页组件 无依赖 跳转到 购物车组件页面


而使用 ARouter 进行简单路由跳转,只有两步:添加注解路径、通过路径路由跳转。


1、在支持路由的页面上添加注解 @Route(path = "/xx/xx"),路径需要注意的是至少需要有两级,/xx/xx。这里就是购物车组件的 CartActivity:


@Route(path = "/cart/cartActivity")public class CartActiv


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


ity extends AppCompatActivity {


@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_cart);}}


2、然后在首页组件的 HomeActivity 发起路由操作—点击按钮跳转到购物车,调用 ARouter.getInstance().build("/xx/xx").navigation()即可:


@Route(path = "/homepage/homeActivity")public class HomeActivity extends AppCompatActivity {


@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_home);


findViewById(R.id.btn_go_cart).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {//通过路由跳转到 购物车组件的购物车页面(但没有依赖购物车组件)ARouter.getInstance().build("/cart/cartActivity").withString("key1","value1")//携带参数 1.withString("key2","value2")//携带参数 2.navigation();}});}}


另外,注意在 HomeActivity 上添加了注解和路径,这是为了壳工程的启动页中直接打开首页。还看到路由跳转可以像 startActivity 一样待参数。


最后,壳工程的启动页中 通过路由打开首页(当然这里也可以用 startActivity(),毕竟壳工程依赖了首页组件):


//启动页 public class SplashActivity extends AppCompatActivity {


@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);


//通过路由直接打开 home 组件的 HomeActivity,ARouter.getInstance().build("/homepage/homeActivity").navigation();finish();}}


我们 run 壳工程 最后看下效果:



到这里,组件间页面跳转的问题也解决了。

五、组件间通信

组件间没有依赖,又如何进行通信呢?


例如,首页需要展示购物车中商品的数量,而查询购物车中商品数量 这个能力是购物车组件内部的,这咋办呢?

5.1 服务暴露组件

平时开发中 我们常用 接口 进行解耦,对接口的实现不用关心,避免接口调用与业务逻辑实现紧密关联。这里组件间的解耦也是相同的思路,仅依赖和调用服务接口,不会依赖接口的实现。


可能你会有疑问了:既然首页组件可以访问购物车组件接口了,那就需要依赖购物车组件啊,这俩组件还是耦合了啊,那咋办啊?答案是组件拆分出可暴露服务。见下图:



左侧是组件间可以调用对方服务 但是有依赖耦合。右侧,发现多了 export_homeexport_cart,这是对应拆分出来的专门用于提供服务的暴露组件。操作说明如下:


  • 暴露组件 只存放 服务接口、服务接口相关的实体类、路由信息、便于服务调用的 util 等

  • 服务调用方 只依赖 服务提供方的 露组件,如 module_home 依赖 export_cart,而不依赖 module_cart

  • 组件 需要依赖 自己的暴露组件,并实现服务接口,如 module_cart 依赖 export_cart 并实现其中的服务接口

  • 接口的实现注入 依然是由 ARouter 完成,和页面跳转一样使用路由信息


下面按照此方案 来实施 首页调用购物车服务 来获取商品数量,更好地说明和理解。

5.2 实施

5.2.1 新建 export_cart

首先,在购物车工程中新建 module 即 export_cart,在其中新建接口类 ICartService 并定义获取购物车商品数量方法,注意接口必须继承 IProvider,是为了使用 ARouter 的实现注入:


/**


  • 购物车组件对外暴露的服务

  • 必须继承 IProvider

  • @author hufeiyang*/public interface ICartService extends IProvider {


/**


  • 获取购物车中商品数量

  • @return*/CartInfo getProductCountInCart();}


CartInfo 是购物车信息,包含商品数量:


/**


  • 购物车信息

  • @author hufeiyang*/public class CartInfo {


/**


  • 商品数量*/public int productCount;}


接着,创建路由表信息,存放购物车组件对外提供跳转的页面、服务的路由地址:


/**


  • 购物车组件路由表

  • 即 购物车组件中 所有可以从外部跳转的页面 的路由信息

  • @author hufeiyang*/public interface CartRouterTable {


/**


  • 购物车页面*/String PATH_PAGE_CART = "/cart/cartActivity";


/**


  • 购物车服务*/String PATH_SERVICE_CART = "/cart/service";


}


前面说页面跳转时是直接使用 路径字符串 进行路由跳转,这里是和服务路由都放在这里统一管理。


然后,为了外部组件使用方便新建 CartServiceUtil:


/**


  • 购物车组件服务工具类

  • 其他组件直接使用此类即可:页面跳转、获取服务。

  • @author hufeiyang*/public class CartServiceUtil {


/**


  • 跳转到购物车页面

  • @param param1

  • @param param2*/public static void navigateCartPage(String param1, String param2){ARouter.getInstance().build(CartRouterTable.PATH_PAGE_CART).withString("key1",param1).withString("key2",param2).navigation();}


/**


  • 获取服务

  • @return*/public static ICartService getService(){//return ARouter.getInstance().navigation(ICartService.class);//如果只有一个实现,这种方式也可以 return (ICartService) ARouter.getInstance().build(CartRouterTable.PATH_SERVICE_CART).navigation();}


/**


  • 获取购物车中商品数量

  • @return*/public static CartInfo getCartProductCount(){return getService().getProductCountInCart();}}


注意到,这里使用静态方法 分别提供了页面跳转、服务获取、服务具体方法获取。 其中服务获取 和页面跳转 同样是使用路由,并且服务接口实现类 也是需要添加 @Route 注解指定路径的。


到这里,export_cart 就已经准备完毕,我们同样发布一个 export_cart 的 ARR(“com.github.hufeiyang.Cart:export_cart:xxx”)。


再来看看 module_cart 对服务接口的实现。

5.2.2 module_cart 的实现

首先,module_cart 需要依赖 export_cart:


//module_cart 的 Build.gradledependencies {...annotationProcessor 'com.alibaba:arouter-compiler:1.2.1'implementation 'com.github.hufeiyang:Common:1.0.0'


//依赖 export_cartimplementation 'com.github.hufeiyang.Cart:export_cart:1.0.5'}


点击 sync 后,接着 CartActivity 的 path 改为路由表提供:


@Route(path = CartRouterTable.PATH_PAGE_CART)public class CartActivity extends AppCompatActivity {


@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_cart);}}


然后,新建服务接口的实现类来实现 ICartService,添加 @Route 注解指定 CartRouterTable 中定义的服务路由


/**


  • 购物车组件服务的实现

  • 需要 @Route 注解、指定 CartRouterTable 中定义的服务路由

  • @author hufeiyang*/@Route(path = CartRouterTable.PATH_SERVICE_CART)public class CartServiceImpl implements ICartService {


@Overridepublic CartInfo getProductCountInCart() {//这里实际项目中 应该是 请求接口 或查询数据库 CartInfo cartInfo = new CartInfo();cartInfo.productCount = 666;return cartInfo;}


@Overridepublic void init(Context context) {//初始化工作,服务注入时会调用,可忽略}}


这里的实现是直接实例化了 CartInfo,数量赋值 666。然后发布一个 ARR(“com.github.hufeiyang.Cart:module_cart:xxx”)。

5.2.3 module_home 中的使用和调试

module_home 需要依赖 export_cart:


//module_home 的 Build.gradledependencies {...annotationProcessor 'com.alibaba:arouter-compiler:1.2.1'implementation 'com.github.hufeiyang:Common:1.0.0'


//注意这里只依赖 export_cart(module_cart 由壳工程引入)implementation 'com.github.hufeiyang.Cart:export_cart:1.0.5'}


在 HomeActivity 中新增 TextView,调用 CartServiceUtil 获取并展示购物车商品数量:


@Route(path = "/homepage/homeActivity")public class HomeActivity extends AppCompatActivity {


@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_home);


//跳转到购物车页面 findViewById(R.id.btn_go_cart).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {//通过路由跳转到 购物车组件的购物车页面(但没有依赖购物车组件)// ARouter.getInstance()// .build("/cart/cartActivity")// .withString("key1","param1")//携带参数 1// .withString("key2","param2")//携带参数 2// .navigation();


CartServiceUtil.navigateCartPage("param1", "param1");}});


//调用购物车组件服务:获取购物车商品数量 TextView tvCartProductCount = findViewById(R.id.tv_cart_product_count);tvCartProductCount.setText("购物车商品数量:"+ CartServiceUtil.getCartProductCount().productCount);}}


看到 使用 CartServiceUtil.getCartProductCount()获取购物车信息并展示,跳转页面也改为了 CartServiceUtil.navigateCartPage()方法。


到这里 home 组件的就可以独立调试了:页面跳转和服务调用,独立调试 ok 后 再集成到壳工程。 先让 HomePage 工程的 app 模块依赖 Common 组件、module_cart 以及本地的 module_home


//HomePage 工程,app 模块的 Build.gradledependencies {...//引入本地 Common 组件、module_cart、module_home,在 app module 中独立调试使用 implementation 'com.github.hufeiyang:Common:1.0.0'implementation 'com.github.hufeiyang.Cart:module_cart:1.0.6'


implementation project(path: ':module_home')}


然后新建 MyApplication 初始化 ARouter、在 app 的 MainActivity 中使用 ARouter.getInstance().build("/homepage/homeActivity").navigation()打开首页,这样就可以调试了。


调试 ok 后接着就是集成到壳工程。

5.2.4 集成到壳工程

壳工程中的操作和独立调试类似,区别是对首页组件引入的是 ARR:


dependencies {...//这里没有使用私有 maven 仓,而是发到 JitPack 仓,一样的意思~// implementation 'com.hfy.cart:cart:1.0.0'implementation 'com.github.hufeiyang.Cart:module_cart:1.0.6'implementation 'com.github.hufeiyang:HomePage:1.0.4'


//壳工程内 也需要依赖 Common 组件,因为需要初始化 ARouterimplementation 'com.github.hufeiyang:Common:1.0.0'}


最后 run 壳工程来看下效果:



获取数量是 666、跳转页面成功。


另外,除了 export_xxx 这种方式,还可以添加一个 ComponentBase 组件,这个组件被所有的 Common 组件依赖,在这个组件中分别添加定义了业务组件可以对外提供访问自身数据的抽象方法的 Service。相当于把各业务组件的 export 整合到 ComponentBase 中,这样就只添加了一个组件而已。但是这样就不好管理了,每个组件对外能力的变更都要改 ComponentBase。


另外,除了组件间方法调用,使用 EventBus 在组件间传递信息也是 ok 的(注意 Event 实体类要定义在 export_xxx 中)。


好了,到这里组件间通信问题也解决了。

六、fragment 实例获取

上面介绍了 Activity 的跳转,我们也会经常使用 Fragment。例如常见的应用主页 HomeActivity 中包含了多个属于不同组件的 Fragment、或者有一个 Fragment 多个组件都需要用到。通常我们直接访问具体 Fragment 类来 new 一个 Fragment 实例,但这里组件间没有直接依赖,那咋办呢?答案依然是 ARouter


先在 module_cart 中创建 CartFragment:


//添加注解 @Route,指定路径 @Route(path = CartRouterTable.PATH_FRAGMENT_CART)public class CartFragment extends Fragment {...public CartFragment() {}@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);...}


@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {//显示“cart_fragment"return inflater.inflate(R.layout.fragment_cart, container, false);}}


同时是 fragment 添加注解 @Route,指定路由路径,路由还是定义在 export_cart 的 CartRouterTable 中,所以 export_cart 需要先发一个 ARR,module_cart 来依赖,然后 module_cart 发布 ARR。


然后再 module_home 中依赖 export_cart,使用 ARouter 获取 Fragment 实例:


@Route(path = "/homepage/homeActivity")public class HomeActivity extends AppCompatActivity {

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
“终于懂了” 系列,android面试简历模板