Android 实现自己的组件化开发

学习目标:

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")));

学习产出:

可以看到我们可以在各个模块之间相互跳转,还可以返回。
在这里插入图片描述
附上代码

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值