学习目标:
Android 实现自己的组件化开发学习内容:
1.为什么需要组件化?
组件化开发可以有效降低代码模块的耦合度,使代码架构更加清晰,同时模块化的编译可以有效减少编译时间,当然总的编译时间是不会减少的,只是App模块化之后开发某个模块时,只需要编译特定模块,可以快速编译调试。
2.组件化和插件化的区别
简单来说组件化是在编译期分模块,插件化是在运行期。一般插件化用于动态修复bug或者动态更新模块,相对来说黑科技更多一些。正常一个App中可以有多个module,但是一般只会有一个module是设置为application的,其他均设置为library,组件化开发就是要每个module都可以运行起来,因此在开发期间每个module均设置为application,发布时再进行合并。
组件化的注意事项:
1.要注意包名和资源文件命名冲突的问题
2.Gradle中的版本号的统一管理
3.组件在Application和Library之间的切换。
4.AndroidManifest.xml文件的曲区分
5.Library不能在Gradle文件中有applicationId
假设我们现在模拟一个场景,我们的有主app模块,login模块,和一个member模块,我们需要用组件化开发来实现,首先当然实现创建咯。创建完之后我们就有三个build.gradle需要我们去配置了,而且都是重复的配置,假如我们有100个这样的模块呢,重复配置100次?而且我们版本如果要升级岂不是就很麻烦了。所以我们需要把共同的属性抽到一个公共的grade文件里,让后其它gradle引用他就可以,这样下次要修改,只要改公共gradle就可以了,那怎么来做呢?如下,首先创建一个以gradle结尾的文件,我就取名叫config.gradle把。
里面的内容如下
//gradle统一参数配置
ext{
// true:正式环境
isRelease = true
android_config=[
compileSdkVersion: 29,
minSdkVersion:19,
targetSdkVersion: 29,
versionCode: 1,
versionName: "1.0",
testInstrumentationRunner: "androidx.test.runner.AndroidJUnitRunner",
consumerProguardFiles: 'consumer-rules.pro'
]
appId = [
"app":"com.suyong.componentization",
"login":"com.suyong.login",
"member":"com.suyong.member"
]
dependencies = [
publicImplemention:[
],
other:[
':annotation',
':arouter'
],
annotationProCessor:[
':annotation-complier'
]
]
}
然后创建完之后你需要在你的整个工程的build.gradle里修改一下,这样的你的自定义配置就会被加载,这样你就可以在别的gradle文件里调用它了
apply from: "config.gradle"
接下来就是在别的gradle文件里使用它了。
login module里的build.gradle
if(isRelease){
//如果是正式版,就变成library
apply plugin: 'com.android.library'
}else {
//否则就是application
apply plugin: 'com.android.application'
}
def config = rootProject.ext.android_config
def appId = rootProject.ext.appId
def support = rootProject.ext.dependencies
android {
compileSdkVersion config.compileSdkVersion
defaultConfig {
if(!isRelease){
applicationId appId.login
}
minSdkVersion config.minSdkVersion
targetSdkVersion config.targetSdkVersion
versionCode config.versionCode
versionName config.versionName
testInstrumentationRunner config.testInstrumentationRunner
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
sourceSets{
main{
if(!isRelease){
manifest.srcFile 'src/main/AndroidManifest.xml'
}else{
manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
}
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
support.publicImplemention.each{ implementation it}
support.other.each{implementation project(it)}
support.annotationProCessor.each{ annotationProcessor project(it) }
}
member module里的build.gradle
if(isRelease){
apply plugin: 'com.android.library'
}else {
apply plugin: 'com.android.application'
}
def config = rootProject.ext.android_config
def appId = rootProject.ext.appId
def support = rootProject.ext.dependencies
android {
compileSdkVersion config.compileSdkVersion
defaultConfig {
if(!isRelease){
applicationId appId.member
}
minSdkVersion config.minSdkVersion
targetSdkVersion config.targetSdkVersion
versionCode config.versionCode
versionName config.versionName
testInstrumentationRunner config.testInstrumentationRunner
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
sourceSets{
main{
if(!isRelease){
manifest.srcFile 'src/main/AndroidManifest.xml'
}else{
manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
}
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
//循环导包
support.publicImplemention.each{ implementation it}
//循环依赖库
support.other.each{implementation project(it)}
//申明注解器
support.annotationProCessor.each{ annotationProcessor project(it) }
}
app里的build.gradle
apply plugin: 'com.android.application'
def config = rootProject.ext.android_config
def appId = rootProject.ext.appId
def support = rootProject.ext.dependencies
android {
compileSdkVersion config.compileSdkVersion
defaultConfig {
applicationId appId.app
minSdkVersion config.minSdkVersion
targetSdkVersion config.targetSdkVersion
versionCode config.versionCode
versionName config.versionName
testInstrumentationRunner config.testInstrumentationRunner
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
implementation project(path: ':annotation-complier')
//主模块不能依赖主模块
if(isRelease){
implementation project(":login")
implementation project(":member")
}
support.publicImplemention.each{ implementation it}
support.other.each{implementation project(it)}
support.annotationProCessor.each{ annotationProcessor project(it) }
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
我们这样就基本配置完成了组件化开发的前期工作,但是我们各个模块毕竟是独立的,那要怎么通信呢,这是个问题,但是我们通过组件化路由框架就可以解决这个问题,这也是组件化开发的一个必不可少的部分呢。不过这期的标题既然都是自己实现的话呢,我们就来自己实现这个组件化路由框架。
其实在上面的工程目录的那张图片里有了提示了,我们有三个模块,一个注解器(annotation),一个(annotation-complier)注解解释器,还有一个(arouter)给应用层调用的。
下面来看一下代码
annotation (java library)
package com.suyong.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//这是注释我们模块里的activity的注解
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface MyARouterPath {
String value();
}
annotation-complier(java library)
package com.suyong.annotion_complier;
import com.google.auto.service.AutoService;
import com.suyong.annotation.MyARouterPath;
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.Processor;
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(Processor.class)
public class AnnotationComplier extends AbstractProcessor {
Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(MyARouterPath.class);
Map<String, String> map = new HashMap<>();
for (Element element : elementsAnnotatedWith) {
TypeElement e = (TypeElement) element;
//包名加类名
String value = e.getQualifiedName().toString();
//注解里的值
String key = e.getAnnotation(MyARouterPath.class).value();
map.put(key, value + ".class");
}
//通过编译时技术自动生成代码
if (map.size() > 0) {
String className = "ARouter" + System.currentTimeMillis();
Writer writer = null;
try {
JavaFileObject sourceFile = filer.createSourceFile("com.suyong.util." + className);
writer = sourceFile.openWriter();
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("package com.suyong.util;\n");
stringBuilder.append("import com.suyong.arouter.ARouter;\n");
stringBuilder.append("import com.suyong.arouter.IARouter;\n");
stringBuilder.append("\n");
stringBuilder.append("public class "+className+" implements IARouter{\n");
stringBuilder.append(" @Override\n");
stringBuilder.append(" public void putActivity() {\n");
Iterator<String> iterator = map.keySet().iterator();
while(iterator.hasNext()){
String key = iterator.next();
String value = map.get(key);
stringBuilder.append(" ARouter.getInstance().addActivity(\""+key+"\","+value+");\n");
}
stringBuilder.append("\n}\n}");
writer.write(stringBuilder.toString());
} catch (IOException e) {
e.printStackTrace();
}finally {
if(writer!=null){
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return false;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return processingEnv.getSourceVersion();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> type = new HashSet<>();
type.add(MyARouterPath.class.getCanonicalName());
return type;
}
}
annotation-complier 下的build.gradle
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//依赖google服务库
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
implementation project(path: ':annotation')
}
sourceCompatibility = "7"
targetCompatibility = "7"
arouter(moudle)
ARouter.java
package com.suyong.arouter;
import android.app.Activity;
import android.content.Context;
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 Map<String, Class<? extends Activity>> map;
private Context mContext;
private static ARouter instance = new ARouter();
private ARouter() {
map = new HashMap<>();
}
public static ARouter getInstance() {
return instance;
}
public void addActivity(String key, Class<? extends Activity> activity) {
if (activity != null && key != null && !map.containsKey(key)) {
map.put(key, activity);
}
}
public void init(Context context) {
mContext = context;
List<String> result = getClassName("com.suyong.util");
for (String s : result) {
try {
Class<?> aClass = Class.forName(s);
//判断这个类是否实现了IARouter
if (IARouter.class.isAssignableFrom(aClass)) {
IARouter iaRouter = (IARouter) aClass.newInstance();
iaRouter.putActivity();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//获取这个包下的所有类(全路径)
private List<String> getClassName(String packageName) {
List<String> result = new ArrayList<>();
String path = null;
try {
//获取apk包路径
path = mContext.getPackageManager().getApplicationInfo(
mContext.getPackageName(), 0).sourceDir;
DexFile dexFile = new DexFile(path);
Enumeration enumeration = dexFile.entries();
//遍历这个apk里的所有class
while (enumeration.hasMoreElements()) {
String name = (String) enumeration.nextElement();
if (name.contains(packageName)) {
result.add(name);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
//获取activity对应的全路径
public Class<?> getActivity(String s) {
if (s != null && map.containsKey(s)) {
return map.get(s);
}
return null;
}
}
IARouter .java
public interface IARouter {
void putActivity();
}
然后我们要给我们的模块里的activity添加路由框架
package com.suyong.login;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import com.suyong.annotation.MyARouterPath;
import com.suyong.arouter.ARouter;
@MyARouterPath("login/login")
public class LoginActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
}
public void jumpToMember(View view){
startActivity(new Intent(this, ARouter.getInstance().getActivity("member/member")));
}
}
package com.suyong.member;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import com.suyong.annotation.MyARouterPath;
@MyARouterPath("member/member")
public class MemberActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memeber);
}
}
然后我们只要加入要跳转app的话,我们首先需要在Application里调用
ARouter.getInstance().init(this);
跳转的时候就用如下代码就可以
//跳转login
startActivity(new Intent(this,ARouter.getInstance().getActivity("login/login")));
//跳转member
startActivity(new Intent(this,ARouter.getInstance().getActivity("login/login")));
学习产出:
可以看到我们可以在各个模块之间相互跳转,还可以返回。
附上代码