组件化开发
前言
什么是组件化?
最初的目的是代码重用,功能相对单一或者独立。在整个系统的代码层次上位于最底层,被其他代码所依赖,所以说组件化是纵向分层。
为什么要使用组件化?
当我们的项目越做越大的时候,有时间就很难去维护,有时候让我们去改点东西,根本实现不了,与其让我们修修改改,还不如自己重新去写代码。
其实无论是大项目也好,或者是中小型项目也好,如果去使用组件化进行开发,那会使你很难割舍,因为它能在你开发中带来很多的便利,包括一些功能解耦和和一些功能的复用
组件化和插件化区别?
其实表面上,它们是没有多大的区别。但是从根本目的上,看出它们的区别。
组件化最终的目的是要实现分开编译,就是每一个功能模块,都可以去编译,去测试
模块化的根本目的并不是说分开编译,而是功能复用这一块
所以,他俩的区别就是,一个为了每个模块都可以去分开编译去运行,例一个是为了我们的功能更加好的被复用
1.如何将项目组件化
学习目标:把项目搭建成组件化开发项目
使用Android Studio 新建一个Android工程。用这个工程里面创建一个个组件。
把目录切换为Project界面,右击项目名 点击 new -->Module --> 选择 Phone&Tablet Module --> 接下来给组件名字,不能取MainActivity,如果取,合并的时候会报错,因为不能有两个Activity 名字相同。此处演示,故使用 t1–>
下一个界面选择Activity初始类型,这个看个人情况,因此处为演示,选默认Empty Activity -->接下来给Activity起名字,组件名+Activity即可 --> 点击Finish加载完成即可
这样,一个组件就创建完成了。
注意点:名字不能与app其他组件一致
2.如何统一管理模块
学习目标模块化开发中,gradle中的版本进行统一管理
方便统一管理所有组件版本号
创建完成后,在Project界面 gradle.properties --> 把下面(1)写进去 -->
打开新建组件和app组件的build.gradle文件进行修改,修改后如(2)
(1).
#具体多少详见自己SDK配置
#最小SDK版本
MIN_SDK_VERSION = 16
#当前SDK版本
TAR_SDK_VERSION = 29
#Android使用的SDK版本
COMPILER_SDK_VERSION = 29
(2).
apply plugin: 'com.android.application'
android {
compileSdkVersion COMPILER_SDK_VERSION.toInteger()
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.example.t1"
minSdkVersion MIN_SDK_VERSION.toInteger()
targetSdkVersion TAR_SDK_VERSION.toInteger()
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
让组件在library和application之间进行转换
Project界面 --> gradle.properties --> 在里面设置变量 IS_APPLICATION = true -->
在新建组建的main文件夹下建立manifest文件夹,并把清单文件复制到里面 (注意:是复制,不是剪切) --> 修改被复制的清单文件,修改后如(1)所示 -->
打开新建组件的build.gradle文件进行修改,主要修改三处,修改后如(2)
(1)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.t1">
<application>
<activity android:name=".t1Activity">
</activity>
</application>
</manifest>
(2).
<1>.
if(IS_APPLICATION.toBoolean()){
apply plugin: 'com.android.application'
}else {
apply plugin: 'com.android.library'
}
<2>.
if(IS_APPLICATION.toBoolean()) {
applicationId "com.zrc.myhome"
}
<3>.
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main{
if(IS_APPLICATION.toBoolean()){
manifest.srcFile 'src/main/AndroidManifest.xml'
}else{
manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
}
}
}
操作完成后,只要修改IS_APPLICATION,就可以控制该组件是library还是application,true为application,false为library
重复上面操作,新创建一个组件,名为t2
让t1,t2与app组件依赖,在打开app组件的build.gradle ,在里面加入以下代码
if(IS_APPLICATION.toBoolean()){
implementation project(path: ':t2')
implementation project(path: ':t1')
}
3.编译时技术的使用&手撸组件化路由框架
学习目标:
1.掌握注解加注解处理器组成的编译时技术
2.使用APT+ANNOTATION以及代理的模式来构架超易用的路由框架
组件创建完成,如果t1组件中的Activity,调用t2组件中的Acticity,要怎么办?
假设我们用平时的跳转操作
它就会报错,提示找不到这个Activity,原因很简单,因为跨包,不可能拿到类对象,因为没有去依赖
想要两个独立的组件有联系,就需要有中间人,这个中间人就是路由框架
创建路由框架
new Module --> Android Library --> arouter --> 让所有组件都依赖arouter
这个中间人(路由框架),必须满足的条件,是两个组件,都必须和它有联系,故让所有组件都依赖arouter 。
使用路由框架,让没有关联的组件通信
现在,所以组件都依赖了arouter,那这个arouter,到底要做什么事情,那它到底怎么做,才能满足中间人的要求?
解决一个问题,不知道怎么办,那我们就回到问题的本质
在t1Activity里面,我们拿不到t2Activity的内容,这才是问题的本质。
既然我们现在拿不到类对象,那我们能不能让arouter把所有的类对象,都存在一个容器里面,假设这个容器,存取了所有Activity类对象,那我们拿t2Activity,那是不是从arouter里面就拿得到啊。
在arouter中新建class作为中介。存储所有Activity,写法同(1) --> 调用如(2)
(1)
package com.example.arouter;
import android.app.Activity;
import java.util.HashMap;
import java.util.Map;
/**
* 中间人 代理
* */
public class ARouter {
//所有的地方,用到的都可以是同一个对象的,都可以定义为单例
private static ARouter aRouter = new ARouter();
//容器 装载所有模块中Activity类对象
private Map<String,Class<?extends Activity>> map;
private ARouter(){
map = new HashMap<>();
}
public static ARouter getInstance(){
return aRouter;
}
/**
* 将类对象加入到容器的方法
* */
public void putActivity(String key,Class<? extends Activity> clazz){
if(key!=null&&clazz!=null){
map.put(key,clazz);
}
}
public Class<? extends Activity> getActivity(String key){
Class<? extends Activity> aClass = map.get(key);
if(aClass!=null){
return aClass;
}
return null;
}
}
(2)
Intent intent = new Intent(t1Activity.this, ARouter.getInstance().getActivity("t2Activity"));
这样,我们就可以通过key获取到t2Activity的类对象。
这样写,没有问题,但是我们回归到框架的本质。框架不仅仅是在这一个项目中用的,我们在其他项目中也能用到。框架存在的意义,是替程序员去写代码。让程序员减少开发时间。那我们为什么把Intent方法一块封装到ARouter里面呢?没必要让它getActivity
优化后的(1),优化后,调用(2)
(1)
package com.example.arouter;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import java.util.HashMap;
import java.util.Map;
/**
* 中间人 代理
* */
public class ARouter {
//所有的地方,用到的都可以是同一个对象的,都可以定义为单例
private static ARouter aRouter = new ARouter();
//容器 装载所有模块中Activity类对象
private Map<String,Class<?extends Activity>> map;
//上下文
private Context context;
private ARouter(){
map = new HashMap<>();
}
public static ARouter getInstance(){
return aRouter;
}
public void init(Context context){
this.context = context;
}
/**
* 将类对象加入到容器的方法
* */
public void putActivity(String key,Class<? extends Activity> clazz){
if(key!=null&&clazz!=null){
map.put(key,clazz);
}
}
/*
* 跳转窗体的方法
* */
public void jumpActivity(String key, Bundle bundle){
Class<? extends Activity> aclass = map.get(key);
if(aclass!=null){
Intent intent = new Intent(context,aclass);
if(bundle!=null){
intent.putExtras(bundle);
}
context.startActivity(intent);
}
}
}
ARouter.getInstance().jumpActivity("t1Activity",null);
框架存在的意义在于,让程序员随意的去扩展,如果一个框架,程序员改不动,那这个框架是有问题的。
问:现在可以进行窗口跳转了吗?
答案是否定的,为什么不行呢?上面那些,都是基于map里面有数据,已经把所有Activity都加入到了map里面,但是现在,我们的map里面,并没有数据。那我们怎么样,才可以让所有Activity都加进去哪?
常规方法:
把所有Activity存入Map --> 在arouter中新建一个接口如(1) -->
在每一个组件中建一个类,在这个类里面继承接口,传入Activity,如(2)
(1)
package com.example.arouter;
public interface IRouter {
void putActivity();
}
(2)
package com.example.t1;
import com.example.arouter.ARouter;
import com.example.arouter.IRouter;
public class ActivityUtil implements IRouter {
@Override
public void putActivity() {
ARouter.getInstance().putActivity("t1Activity",t1Activity.class);
}
}
虽然说,这种方式,能够让我们拿到所有的Activity放到map里面去,但是这样些,管理起来非常麻烦,一点也不自动化,如果有100个组件,一个个去写,那工程量是非常大的。那有没有方式,程序员不用写,程序自动处理,答案是肯定的,这就是我们的,编译时技术。
编译时技术,技术在编译时,自动帮助我们生成一些类。它有两个非常重要的概念——注解和注解处理器
注解有一个非常重要的意义,就是标识,标识你这个类到底要干嘛,就想看到@Override,就知道这个方法是重写的方法,所以,给Activity加上注解,就可以说明它是需要加入到map里面去的。注解类似于牌匾。
首先,我们键两个模块,这两个模块,都必须是javaLibrary,最新版本是java or kotlin Library,注解名为annotations,注解处理器为annotations_compller,创建完成后,让所有模块都依赖这两个模块
注意点:注解处理器的依赖方式,不是implementation,而是
annotationProcessor,我们依赖后还需要去build.gradle文件中去修改。
注解 --> 新建javaLibrary --> annotations --> 新建Bing类,代码如(1) -->注解写法如(2),在类上面写
(1)
package com.example.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE}) //声明注解要放在什么地方 作用域 注解在类上面
@Retention(RetentionPolicy.CLASS) //声明注解存在的时间 生命周期 源码期 < 编译器 < 运行期
public @interface BindPath {
String value();
}
(2)
@BindPath("t1/t1")
现在注解有了,我们来看我们的注解处理器annotations_compller。首先我们要去注册,注册使用到一个api,我们需要引入一个谷歌的第三方库。
在Android Studio以上,写
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
compileOnly 'com.google.auto.service:auto-service:1.0-rc3'
以下的话,只需要写
implementation 'com.google.auto.service:auto-service:1.0-rc3'
就可以了!
创建AnnotationCompiler类,写法如(1)
package com.example.annotation_compller;
import com.example.annotations.BindPath;
import com.google.auto.service.AutoService;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;
/**
* 这个类存在的意义:生成类,写代码
* */
@AutoService(Process.class) //注册注解管理器
public class AnnotationCompiler extends AbstractProcessor {
//生成文件的对象
Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filer = processingEnvironment.getFiler();
}
/**
* 声明注解处理器支持的java版本
* */
@Override
public SourceVersion getSupportedSourceVersion() {
return processingEnv.getSourceVersion();
}
/**
* 声明注解处理器要声明的注解有哪些
* */
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new HashSet<>();
types.add(BindPath.class.getCanonicalName());
//如果有更多的注解,重复上面的话就可以了
return types;
}
/**
* 生成类,写代码
* */
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindPath.class);
// TypeElement 类节点
// ExecutableElement 方法节点
// VariableElement 成员变量节点
Map<String,String> map = new HashMap<>();
for (Element element : elementsAnnotatedWith){
TypeElement typeElement = (TypeElement) element;
//得到这个节点上面的注解里面的参数
String key = typeElement.getAnnotation(BindPath.class).value();
String activityName = typeElement.getQualifiedName().toString();
map.put(key,activityName+".class");
}
if(map.size()>0){
Writer writer = null;
//创建一个新类名
String activityName = "ActivityUtil" + System.currentTimeMillis();
try {
//生成文件
JavaFileObject sourceFile = filer.createSourceFile("com.example.util" + activityName);
writer = sourceFile.openWriter();
writer.write("package com.example.util;\n" +
"\n" +
"import com.example.arouter.ARouter;\n" +
"import com.example.arouter.IRouter;\n" +
"\n" +
"\n" +
"public class "+activityName+" implements IRouter {\n" +
" @Override\n" +
" public void putActivity() {");
Iterator<String> iterator = map.keySet().iterator();
System.out.println("111111111111111");
while (iterator.hasNext()){
String keys = iterator.next();
String activityNames = map.get(keys);
writer.write("ARouter.getInstance().putActivity(\""+keys+"\","+
activityNames+");\n");
}
writer.write("}\n}");
} catch (Exception e) {
e.printStackTrace();
}finally {
if(writer!=null){
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return false;
}
}
在arouter中的Arouter里面添加代码
package com.example.arouter;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import dalvik.system.DexFile;
/**
* 中间人 代理
* */
public class ARouter {
//所有的地方,用到的都可以是同一个对象的,都可以定义为单例
private static ARouter aRouter = new ARouter();
//容器 装载所有模块中Activity类对象
private Map<String,Class<?extends Activity>> map;
//上下文
private Context context;
private ARouter(){
map = new HashMap<>();
}
public static ARouter getInstance(){
return aRouter;
}
public void init(Context context){
this.context = context;
// 根据包名获取到这个包下面所有的类
List<String> className = getClassName("com.example.util");
for(String s: className){
try{
Class<?> aClass = Class.forName(s);
//判断这个类是否是IRouter的接口
if(IRouter.class.isAssignableFrom(aClass)){
IRouter iRouter = (IRouter) aClass.newInstance();
iRouter.putActivity();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
/**
* 将类对象加入到容器的方法
* */
public void putActivity(String key,Class<? extends Activity> clazz){
if(key!=null&&clazz!=null){
map.put(key,clazz);
}
}
/*
* 跳转窗体的方法
* */
public void jumpActivity(String key, Bundle bundle){
Class<? extends Activity> aclass = map.get(key);
if(aclass!=null){
Intent intent = new Intent(context,aclass);
if(bundle!=null){
intent.putExtras(bundle);
}
context.startActivity(intent);
}
}
private List<String> getClassName(String packageName){
//创建一个class对象的集合
List<String> classList = new ArrayList<>();
String path= null;
try {
//通过包的管理器 获取到应用信息类后获取到APK的完整路径
path = context.getPackageManager().getApplicationInfo(context.getPackageName(),0).sourceDir;
//根据APK的完整路径获取到编译后的dex文件
DexFile dexFile = new DexFile(path);
//获取编译后的dex文件中的所有class
Enumeration entries = dexFile.entries();
//然后遍历
while(entries.hasMoreElements()){
//通过遍历所有的class的类名
String name = (String) entries.nextElement();
//判断类的包名,是否符合com.example.util
if(name.contains(packageName)){
//如果符合 就添加到集合中
classList.add(name);
}
}
}catch (Exception e){
e.printStackTrace();
}
return classList;
}
}
然后在app模块里面,创建MyApplication,代码如下:
package com.example.t03_22_02;
import android.app.Application;
import com.example.arouter.ARouter;
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
ARouter.getInstance().init(this);
}
}
然后在清单文件中写入
完工!!!